{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deep Learning with PyTorch Step-by-Step: A Beginner's Guide"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 2.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    import google.colab\n",
    "    import requests\n",
    "    url = 'https://raw.githubusercontent.com/dvgodoy/PyTorchStepByStep/master/config.py'\n",
    "    r = requests.get(url, allow_redirects=True)\n",
    "    open('config.py', 'wb').write(r.content)    \n",
    "except ModuleNotFoundError:\n",
    "    pass\n",
    "\n",
    "from config import *\n",
    "config_chapter2_1()\n",
    "# This is needed to render the plots in this chapter\n",
    "from plots.chapter2_1 import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import datetime\n",
    "\n",
    "import torch\n",
    "import torch.optim as optim\n",
    "import torch.nn as nn\n",
    "import torch.functional as F\n",
    "from torch.utils.data import DataLoader, TensorDataset, random_split\n",
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "plt.style.use('fivethirtyeight')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Going Classy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# A completely empty (and useless) class\n",
    "class StepByStep(object):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Constructor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Arguments"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class StepByStep(object):\n",
    "    def __init__(self, model, loss_fn, optimizer):\n",
    "        # Here we define the attributes of our class\n",
    "        \n",
    "        # We start by storing the arguments as attributes \n",
    "        # to use them later\n",
    "        self.model = model\n",
    "        self.loss_fn = loss_fn\n",
    "        self.optimizer = optimizer\n",
    "        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "        # Let's send the model to the specified device right away\n",
    "        self.model.to(self.device)\n",
    "        \n",
    "    def to(self, device):\n",
    "        # This method allows the user to specify a different device\n",
    "        # It sets the corresponding attribute (to be used later in\n",
    "        # the mini-batches) and sends the model to the device\n",
    "        try:\n",
    "            self.device = device\n",
    "            self.model.to(self.device)\n",
    "        except RuntimeError:\n",
    "            self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "            print(f\"Couldn't send it to {device}, sending it to {self.device} instead.\")\n",
    "            self.model.to(self.device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Placeholders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class StepByStep(object):\n",
    "    def __init__(self, model, loss_fn, optimizer):\n",
    "        # Here we define the attributes of our class\n",
    "        \n",
    "        # We start by storing the arguments as attributes \n",
    "        # to use them later\n",
    "        self.model = model\n",
    "        self.loss_fn = loss_fn\n",
    "        self.optimizer = optimizer\n",
    "        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "        # Let's send the model to the specified device right away\n",
    "        self.model.to(self.device)\n",
    "        \n",
    "        # These attributes are defined here, but since they are\n",
    "        # not available at the moment of creation, we keep them None\n",
    "        self.train_loader = None\n",
    "        self.val_loader = None\n",
    "        self.writer = None\n",
    "\n",
    "    def to(self, device):\n",
    "        # This method allows the user to specify a different device\n",
    "        # It sets the corresponding attribute (to be used later in\n",
    "        # the mini-batches) and sends the model to the device\n",
    "        try:\n",
    "            self.device = device\n",
    "            self.model.to(self.device)\n",
    "        except RuntimeError:\n",
    "            self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "            print(f\"Couldn't send it to {device}, sending it to {self.device} instead.\")\n",
    "            self.model.to(self.device)\n",
    "\n",
    "    def set_loaders(self, train_loader, val_loader=None):\n",
    "        # This method allows the user to define which train_loader \n",
    "        # (and val_loader, optionally) to use\n",
    "        # Both loaders are then assigned to attributes of the class\n",
    "        # So they can be referred to later\n",
    "        self.train_loader = train_loader\n",
    "        self.val_loader = val_loader\n",
    "\n",
    "    def set_tensorboard(self, name, folder='runs'):\n",
    "        # This method allows the user to create a SummaryWriter to \n",
    "        # interface with TensorBoard\n",
    "        suffix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n",
    "        self.writer = SummaryWriter(f'{folder}/{name}_{suffix}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class StepByStep(object):\n",
    "    def __init__(self, model, loss_fn, optimizer):\n",
    "        # Here we define the attributes of our class\n",
    "        \n",
    "        # We start by storing the arguments as attributes \n",
    "        # to use them later\n",
    "        self.model = model\n",
    "        self.loss_fn = loss_fn\n",
    "        self.optimizer = optimizer\n",
    "        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "        # Let's send the model to the specified device right away\n",
    "        self.model.to(self.device)\n",
    "        \n",
    "        # These attributes are defined here, but since they are\n",
    "        # not available at the moment of creation, we keep them None\n",
    "        self.train_loader = None\n",
    "        self.val_loader = None\n",
    "        self.writer = None\n",
    "\n",
    "        # These attributes are going to be computed internally\n",
    "        self.losses = []\n",
    "        self.val_losses = []\n",
    "        self.total_epochs = 0\n",
    "\n",
    "    def to(self, device):\n",
    "        # This method allows the user to specify a different device\n",
    "        # It sets the corresponding attribute (to be used later in\n",
    "        # the mini-batches) and sends the model to the device\n",
    "        try:\n",
    "            self.device = device\n",
    "            self.model.to(self.device)\n",
    "        except RuntimeError:\n",
    "            self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "            print(f\"Couldn't send it to {device}, sending it to {self.device} instead.\")\n",
    "            self.model.to(self.device)\n",
    "\n",
    "    def set_loaders(self, train_loader, val_loader=None):\n",
    "        # This method allows the user to define which train_loader \n",
    "        # (and val_loader, optionally) to use\n",
    "        # Both loaders are then assigned to attributes of the class\n",
    "        # So they can be referred to later\n",
    "        self.train_loader = train_loader\n",
    "        self.val_loader = val_loader\n",
    "\n",
    "    def set_tensorboard(self, name, folder='runs'):\n",
    "        # This method allows the user to create a SummaryWriter to \n",
    "        # interface with TensorBoard\n",
    "        suffix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n",
    "        self.writer = SummaryWriter(f'{folder}/{name}_{suffix}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class StepByStep(object):\n",
    "    def __init__(self, model, loss_fn, optimizer):\n",
    "        # Here we define the attributes of our class\n",
    "        \n",
    "        # We start by storing the arguments as attributes \n",
    "        # to use them later\n",
    "        self.model = model\n",
    "        self.loss_fn = loss_fn\n",
    "        self.optimizer = optimizer\n",
    "        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "        # Let's send the model to the specified device right away\n",
    "        self.model.to(self.device)\n",
    "        \n",
    "        # These attributes are defined here, but since they are\n",
    "        # not available at the moment of creation, we keep them None\n",
    "        self.train_loader = None\n",
    "        self.val_loader = None\n",
    "        self.writer = None\n",
    "\n",
    "        # These attributes are going to be computed internally\n",
    "        self.losses = []\n",
    "        self.val_losses = []\n",
    "        self.total_epochs = 0\n",
    "\n",
    "        # Creates the train_step function for our model, \n",
    "        # loss function and optimizer\n",
    "        # Note: there are NO ARGS there! It makes use of the class\n",
    "        # attributes directly\n",
    "        self.train_step_fn = self._make_train_step()\n",
    "        # Creates the val_step function for our model and loss\n",
    "        self.val_step_fn = self._make_val_step()\n",
    "\n",
    "    def to(self, device):\n",
    "        # This method allows the user to specify a different device\n",
    "        # It sets the corresponding attribute (to be used later in\n",
    "        # the mini-batches) and sends the model to the device\n",
    "        try:\n",
    "            self.device = device\n",
    "            self.model.to(self.device)\n",
    "        except RuntimeError:\n",
    "            self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "            print(f\"Couldn't send it to {device}, sending it to {self.device} instead.\")\n",
    "            self.model.to(self.device)\n",
    "\n",
    "    def set_loaders(self, train_loader, val_loader=None):\n",
    "        # This method allows the user to define which train_loader \n",
    "        # (and val_loader, optionally) to use\n",
    "        # Both loaders are then assigned to attributes of the class\n",
    "        # So they can be referred to later\n",
    "        self.train_loader = train_loader\n",
    "        self.val_loader = val_loader\n",
    "\n",
    "    def set_tensorboard(self, name, folder='runs'):\n",
    "        # This method allows the user to create a SummaryWriter to \n",
    "        # interface with TensorBoard\n",
    "        suffix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n",
    "        self.writer = SummaryWriter(f'{folder}/{name}_{suffix}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step Methods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _make_train_step_fn(self):\n",
    "    # This method does not need ARGS... it can refer to\n",
    "    # the attributes: self.model, self.loss_fn and self.optimizer\n",
    "\n",
    "    # Builds function that performs a step in the train loop\n",
    "    def perform_train_step_fn(x, y):\n",
    "        # Sets model to TRAIN mode\n",
    "        self.model.train()\n",
    "\n",
    "        # Step 1 - Computes our model's predicted output - forward pass\n",
    "        yhat = self.model(x)\n",
    "        # Step 2 - Computes the loss\n",
    "        loss = self.loss_fn(yhat, y)\n",
    "        # Step 3 - Computes gradients for both \"b\" and \"w\" parameters\n",
    "        loss.backward()\n",
    "        # Step 4 - Updates parameters using gradients and the\n",
    "        # learning rate\n",
    "        self.optimizer.step()\n",
    "        self.optimizer.zero_grad()\n",
    "\n",
    "        # Returns the loss\n",
    "        return loss.item()\n",
    "\n",
    "    # Returns the function that will be called inside the train loop\n",
    "    return perform_train_step_fn\n",
    "\n",
    "def _make_val_step_fn(self):\n",
    "    # Builds function that performs a step in the validation loop\n",
    "    def perform_val_step_fn(x, y):\n",
    "        # Sets model to EVAL mode\n",
    "        self.model.eval()\n",
    "\n",
    "        # Step 1 - Computes our model's predicted output - forward pass\n",
    "        yhat = self.model(x)\n",
    "        # Step 2 - Computes the loss\n",
    "        loss = self.loss_fn(yhat, y)\n",
    "        # There is no need to compute Steps 3 and 4, \n",
    "        # since we don't update parameters during evaluation\n",
    "        return loss.item()\n",
    "\n",
    "    return perform_val_step_fn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ATTENTION! Using SETATTR for educational purposes only :-)\n",
    "setattr(StepByStep, '_make_train_step_fn', _make_train_step_fn)\n",
    "setattr(StepByStep, '_make_val_step_fn', _make_val_step_fn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### setattr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rex\n"
     ]
    }
   ],
   "source": [
    "rex = Dog('Rex')\n",
    "print(rex.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def bark(dog):\n",
    "    print('{} barks: \"Woof!\"'.format(dog.name))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rex barks: \"Woof!\"\n"
     ]
    }
   ],
   "source": [
    "bark(rex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def bark(self):\n",
    "    print('{} barks: \"Woof!\"'.format(self.name))\n",
    "\n",
    "setattr(Dog, 'bark', bark)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fido barks: \"Woof!\"\n"
     ]
    }
   ],
   "source": [
    "fido = Dog('Fido')\n",
    "fido.bark()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rex barks: \"Woof!\"\n"
     ]
    }
   ],
   "source": [
    "rex.bark()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training Methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mini-Batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _mini_batch(self, validation=False):\n",
    "    # The mini-batch can be used with both loaders\n",
    "    # The argument `validation`defines which loader and \n",
    "    # corresponding step function is going to be used\n",
    "    if validation:\n",
    "        data_loader = self.val_loader\n",
    "        step_fn = self.val_step_fn\n",
    "    else:\n",
    "        data_loader = self.train_loader\n",
    "        step_fn = self.train_step_fn\n",
    "\n",
    "    if data_loader is None:\n",
    "        return None\n",
    "\n",
    "    # Once the data loader and step function, this is the same\n",
    "    # mini-batch loop we had before\n",
    "    mini_batch_losses = []\n",
    "    for x_batch, y_batch in data_loader:\n",
    "        x_batch = x_batch.to(self.device)\n",
    "        y_batch = y_batch.to(self.device)\n",
    "\n",
    "        mini_batch_loss = step_fn(x_batch, y_batch)\n",
    "        mini_batch_losses.append(mini_batch_loss)\n",
    "\n",
    "    loss = np.mean(mini_batch_losses)\n",
    "\n",
    "    return loss\n",
    "\n",
    "setattr(StepByStep, '_mini_batch', _mini_batch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training Loop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_seed(self, seed=42):\n",
    "    torch.backends.cudnn.deterministic = True\n",
    "    torch.backends.cudnn.benchmark = False    \n",
    "    torch.manual_seed(seed)\n",
    "    np.random.seed(seed)\n",
    "    \n",
    "setattr(StepByStep, 'set_seed', set_seed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(self, n_epochs, seed=42):\n",
    "    # To ensure reproducibility of the training process\n",
    "    self.set_seed(seed)\n",
    "    \n",
    "    for epoch in range(n_epochs):\n",
    "        # Keeps track of the numbers of epochs\n",
    "        # by updating the corresponding attribute\n",
    "        self.total_epochs += 1\n",
    "\n",
    "        # inner loop\n",
    "        # Performs training using mini-batches\n",
    "        loss = self._mini_batch(validation=False)\n",
    "        self.losses.append(loss)\n",
    "\n",
    "        # VALIDATION\n",
    "        # no gradients in validation!\n",
    "        with torch.no_grad():\n",
    "            # Performs evaluation using mini-batches\n",
    "            val_loss = self._mini_batch(validation=True)\n",
    "            self.val_losses.append(val_loss)\n",
    "\n",
    "        # If a SummaryWriter has been set...\n",
    "        if self.writer:\n",
    "            scalars = {'training': loss}\n",
    "            if val_loss is not None:\n",
    "                scalars.update({'validation': val_loss})\n",
    "            # Records both losses for each epoch under the main tag \"loss\"\n",
    "            self.writer.add_scalars(main_tag='loss',\n",
    "                                    tag_scalar_dict=scalars,\n",
    "                                    global_step=epoch)\n",
    "\n",
    "    if self.writer:\n",
    "        # Flushes the writer\n",
    "        self.writer.flush()\n",
    "        \n",
    "setattr(StepByStep, 'train', train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Saving and Loading Methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Saving"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_checkpoint(self, filename):\n",
    "    # Builds dictionary with all elements for resuming training\n",
    "    checkpoint = {'epoch': self.total_epochs,\n",
    "                  'model_state_dict': self.model.state_dict(),\n",
    "                  'optimizer_state_dict': self.optimizer.state_dict(),\n",
    "                  'loss': self.losses,\n",
    "                  'val_loss': self.val_losses}\n",
    "\n",
    "    torch.save(checkpoint, filename)\n",
    "    \n",
    "setattr(StepByStep, 'save_checkpoint', save_checkpoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Loading"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_checkpoint(self, filename):\n",
    "    # Loads dictionary\n",
    "    checkpoint = torch.load(filename, weights_only=False)\n",
    "\n",
    "    # Restore state for model and optimizer\n",
    "    self.model.load_state_dict(checkpoint['model_state_dict'])\n",
    "    self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n",
    "\n",
    "    self.total_epochs = checkpoint['epoch']\n",
    "    self.losses = checkpoint['loss']\n",
    "    self.val_losses = checkpoint['val_loss']\n",
    "\n",
    "    self.model.train() # always use TRAIN for resuming training   \n",
    "    \n",
    "setattr(StepByStep, 'load_checkpoint', load_checkpoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Making Predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(self, x):\n",
    "    # Set is to evaluation mode for predictions\n",
    "    self.model.eval() \n",
    "    # Takes aNumpy input and make it a float tensor\n",
    "    x_tensor = torch.as_tensor(x).float()\n",
    "    # Send input to device and uses model for prediction\n",
    "    y_hat_tensor = self.model(x_tensor.to(self.device))\n",
    "    # Set it back to train mode\n",
    "    self.model.train()\n",
    "    # Detaches it, brings it to CPU and back to Numpy\n",
    "    return y_hat_tensor.detach().cpu().numpy()\n",
    "\n",
    "setattr(StepByStep, 'predict', predict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualization Methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Losses"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_losses(self):\n",
    "    fig = plt.figure(figsize=(10, 4))\n",
    "    plt.plot(self.losses, label='Training Loss', c='b')\n",
    "    if self.val_loader:\n",
    "        plt.plot(self.val_losses, label='Validation Loss', c='r')\n",
    "    plt.yscale('log')\n",
    "    plt.xlabel('Epochs')\n",
    "    plt.ylabel('Loss')\n",
    "    plt.legend()\n",
    "    plt.tight_layout()\n",
    "    return fig\n",
    "\n",
    "setattr(StepByStep, 'plot_losses', plot_losses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_graph(self):\n",
    "    if self.train_loader and self.writer:\n",
    "        # Fetches a single mini-batch so we can use add_graph\n",
    "        x_sample, y_sample = next(iter(self.train_loader))\n",
    "        self.writer.add_graph(self.model, x_sample.to(self.device))\n",
    "    \n",
    "setattr(StepByStep, 'add_graph', add_graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Full Code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load stepbystep/v0.py\n",
    "\n",
    "import numpy as np\n",
    "import datetime\n",
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "plt.style.use('fivethirtyeight')\n",
    "\n",
    "class StepByStep(object):\n",
    "    def __init__(self, model, loss_fn, optimizer):\n",
    "        # Here we define the attributes of our class\n",
    "        \n",
    "        # We start by storing the arguments as attributes \n",
    "        # to use them later\n",
    "        self.model = model\n",
    "        self.loss_fn = loss_fn\n",
    "        self.optimizer = optimizer\n",
    "        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "        # Let's send the model to the specified device right away\n",
    "        self.model.to(self.device)\n",
    "\n",
    "        # These attributes are defined here, but since they are\n",
    "        # not informed at the moment of creation, we keep them None\n",
    "        self.train_loader = None\n",
    "        self.val_loader = None\n",
    "        self.writer = None\n",
    "        \n",
    "        # These attributes are going to be computed internally\n",
    "        self.losses = []\n",
    "        self.val_losses = []\n",
    "        self.total_epochs = 0\n",
    "\n",
    "        # Creates the train_step function for our model, \n",
    "        # loss function and optimizer\n",
    "        # Note: there are NO ARGS there! It makes use of the class\n",
    "        # attributes directly\n",
    "        self.train_step_fn = self._make_train_step_fn()\n",
    "        # Creates the val_step function for our model and loss\n",
    "        self.val_step_fn = self._make_val_step_fn()\n",
    "\n",
    "    def to(self, device):\n",
    "        # This method allows the user to specify a different device\n",
    "        # It sets the corresponding attribute (to be used later in\n",
    "        # the mini-batches) and sends the model to the device\n",
    "        try:\n",
    "            self.device = device\n",
    "            self.model.to(self.device)\n",
    "        except RuntimeError:\n",
    "            self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "            print(f\"Couldn't send it to {device}, sending it to {self.device} instead.\")\n",
    "            self.model.to(self.device)\n",
    "\n",
    "    def set_loaders(self, train_loader, val_loader=None):\n",
    "        # This method allows the user to define which train_loader (and val_loader, optionally) to use\n",
    "        # Both loaders are then assigned to attributes of the class\n",
    "        # So they can be referred to later\n",
    "        self.train_loader = train_loader\n",
    "        self.val_loader = val_loader\n",
    "\n",
    "    def set_tensorboard(self, name, folder='runs'):\n",
    "        # This method allows the user to define a SummaryWriter to interface with TensorBoard\n",
    "        suffix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n",
    "        self.writer = SummaryWriter(f'{folder}/{name}_{suffix}')\n",
    "\n",
    "    def _make_train_step_fn(self):\n",
    "        # This method does not need ARGS... it can refer to\n",
    "        # the attributes: self.model, self.loss_fn and self.optimizer\n",
    "        \n",
    "        # Builds function that performs a step in the train loop\n",
    "        def perform_train_step_fn(x, y):\n",
    "            # Sets model to TRAIN mode\n",
    "            self.model.train()\n",
    "\n",
    "            # Step 1 - Computes our model's predicted output - forward pass\n",
    "            yhat = self.model(x)\n",
    "            # Step 2 - Computes the loss\n",
    "            loss = self.loss_fn(yhat, y)\n",
    "            # Step 3 - Computes gradients for both \"a\" and \"b\" parameters\n",
    "            loss.backward()\n",
    "            # Step 4 - Updates parameters using gradients and the learning rate\n",
    "            self.optimizer.step()\n",
    "            self.optimizer.zero_grad()\n",
    "\n",
    "            # Returns the loss\n",
    "            return loss.item()\n",
    "\n",
    "        # Returns the function that will be called inside the train loop\n",
    "        return perform_train_step_fn\n",
    "    \n",
    "    def _make_val_step_fn(self):\n",
    "        # Builds function that performs a step in the validation loop\n",
    "        def perform_val_step_fn(x, y):\n",
    "            # Sets model to EVAL mode\n",
    "            self.model.eval()\n",
    "\n",
    "            # Step 1 - Computes our model's predicted output - forward pass\n",
    "            yhat = self.model(x)\n",
    "            # Step 2 - Computes the loss\n",
    "            loss = self.loss_fn(yhat, y)\n",
    "            # There is no need to compute Steps 3 and 4, \n",
    "            # since we don't update parameters during evaluation\n",
    "            return loss.item()\n",
    "\n",
    "        return perform_val_step_fn\n",
    "            \n",
    "    def _mini_batch(self, validation=False):\n",
    "        # The mini-batch can be used with both loaders\n",
    "        # The argument `validation`defines which loader and \n",
    "        # corresponding step function is going to be used\n",
    "        if validation:\n",
    "            data_loader = self.val_loader\n",
    "            step_fn = self.val_step_fn\n",
    "        else:\n",
    "            data_loader = self.train_loader\n",
    "            step_fn = self.train_step_fn\n",
    "\n",
    "        if data_loader is None:\n",
    "            return None\n",
    "            \n",
    "        # Once the data loader and step function, this is the \n",
    "        # same mini-batch loop we had before\n",
    "        mini_batch_losses = []\n",
    "        for x_batch, y_batch in data_loader:\n",
    "            x_batch = x_batch.to(self.device)\n",
    "            y_batch = y_batch.to(self.device)\n",
    "\n",
    "            mini_batch_loss = step_fn(x_batch, y_batch)\n",
    "            mini_batch_losses.append(mini_batch_loss)\n",
    "\n",
    "        loss = np.mean(mini_batch_losses)\n",
    "        return loss\n",
    "\n",
    "    def set_seed(self, seed=42):\n",
    "        torch.backends.cudnn.deterministic = True\n",
    "        torch.backends.cudnn.benchmark = False    \n",
    "        torch.manual_seed(seed)\n",
    "        np.random.seed(seed)\n",
    "    \n",
    "    def train(self, n_epochs, seed=42):\n",
    "        # To ensure reproducibility of the training process\n",
    "        self.set_seed(seed)\n",
    "\n",
    "        for epoch in range(n_epochs):\n",
    "            # Keeps track of the numbers of epochs\n",
    "            # by updating the corresponding attribute\n",
    "            self.total_epochs += 1\n",
    "\n",
    "            # inner loop\n",
    "            # Performs training using mini-batches\n",
    "            loss = self._mini_batch(validation=False)\n",
    "            self.losses.append(loss)\n",
    "\n",
    "            # VALIDATION\n",
    "            # no gradients in validation!\n",
    "            with torch.no_grad():\n",
    "                # Performs evaluation using mini-batches\n",
    "                val_loss = self._mini_batch(validation=True)\n",
    "                self.val_losses.append(val_loss)\n",
    "\n",
    "            # If a SummaryWriter has been set...\n",
    "            if self.writer:\n",
    "                scalars = {'training': loss}\n",
    "                if val_loss is not None:\n",
    "                    scalars.update({'validation': val_loss})\n",
    "                # Records both losses for each epoch under the main tag \"loss\"\n",
    "                self.writer.add_scalars(main_tag='loss',\n",
    "                                        tag_scalar_dict=scalars,\n",
    "                                        global_step=epoch)\n",
    "\n",
    "        if self.writer:\n",
    "            # Closes the writer\n",
    "            self.writer.close()\n",
    "\n",
    "    def save_checkpoint(self, filename):\n",
    "        # Builds dictionary with all elements for resuming training\n",
    "        checkpoint = {'epoch': self.total_epochs,\n",
    "                      'model_state_dict': self.model.state_dict(),\n",
    "                      'optimizer_state_dict': self.optimizer.state_dict(),\n",
    "                      'loss': self.losses,\n",
    "                      'val_loss': self.val_losses}\n",
    "\n",
    "        torch.save(checkpoint, filename)\n",
    "\n",
    "    def load_checkpoint(self, filename):\n",
    "        # Loads dictionary\n",
    "        checkpoint = torch.load(filename, weights_only=False)\n",
    "\n",
    "        # Restore state for model and optimizer\n",
    "        self.model.load_state_dict(checkpoint['model_state_dict'])\n",
    "        self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n",
    "\n",
    "        self.total_epochs = checkpoint['epoch']\n",
    "        self.losses = checkpoint['loss']\n",
    "        self.val_losses = checkpoint['val_loss']\n",
    "\n",
    "        self.model.train() # always use TRAIN for resuming training   \n",
    "\n",
    "    def predict(self, x):\n",
    "        # Set is to evaluation mode for predictions\n",
    "        self.model.eval() \n",
    "        # Takes aNumpy input and make it a float tensor\n",
    "        x_tensor = torch.as_tensor(x).float()\n",
    "        # Send input to device and uses model for prediction\n",
    "        y_hat_tensor = self.model(x_tensor.to(self.device))\n",
    "        # Set it back to train mode\n",
    "        self.model.train()\n",
    "        # Detaches it, brings it to CPU and back to Numpy\n",
    "        return y_hat_tensor.detach().cpu().numpy()\n",
    "\n",
    "    def plot_losses(self):\n",
    "        fig = plt.figure(figsize=(10, 4))\n",
    "        plt.plot(self.losses, label='Training Loss', c='b')\n",
    "        plt.plot(self.val_losses, label='Validation Loss', c='r')\n",
    "        plt.yscale('log')\n",
    "        plt.xlabel('Epochs')\n",
    "        plt.ylabel('Loss')\n",
    "        plt.legend()\n",
    "        plt.tight_layout()\n",
    "        return fig\n",
    "\n",
    "    def add_graph(self):\n",
    "        # Fetches a single mini-batch so we can use add_graph\n",
    "        if self.train_loader and self.writer:\n",
    "            x_sample, y_sample = next(iter(self.train_loader))\n",
    "            self.writer.add_graph(self.model, x_sample.to(self.device))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Classy Pipeline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Data Generation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Runs data generation - so we do not need to copy code here\n",
    "%run -i data_generation/simple_linear_regression.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaAAAAGgCAYAAADsNrNZAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAA/iklEQVR4nO3deVhV1f4/8DczRyFAAhxBRRxwyCFRQSWENNKrZhIOddO69lXQb9dfmkN2kyYlzQZN9Ep+y66YkppYDmUiIoOWc1pKgQjGpAQCMnN+f3DPicOZx30OvF/P4/PEPnvvs84i9uestT5rLauysjIxiIiITMxa6AIQEVH7xABERESCYAAiIiJBMAAREZEgGICIiEgQDEBERCQIBiBq01JTU+Hq6opFixYJXRRSYNGiRXB1dUVubq70WG5uLn9n7QQDkBHk5ORgzZo1CA4ORs+ePfHwww/Dx8cHISEhWLVqFS5cuCB0EQXl6uqKwYMHC10MhSQPv5b/vLy80KdPH0yYMAFLly7FqVOnIBYbZvrc7t274erqinXr1hnkfsYwePBguTpp+W/lypWClo+/M+UkdTN58mShi6KQrdAFaGs2bdqEd955B42NjRgyZAhmzJgBNzc3VFRU4Pr169i5cyfi4uIQExODl19+WejikhIPPfSQ9Bt4Y2MjysrK8Msvv+CLL77A//3f/2HUqFHYvn07evbsKWxBTWjhwoVwcXGROz5y5EgBSiOPvzPLwwBkQB988AHefPNNdO/eHfHx8Rg9erTcOaWlpdi2bRsqKioEKCFpysXFBatWrZI7XlhYiGXLluGbb77B1KlTkZycDHd3dwFKaHqLFi2Cj4+P0MVQir8zy8MuOAPJzc3Fu+++C3t7eyQmJioMPgDQqVMnrF69WmG3RVNTE3bt2oVJkybB29sbXl5eGDNmDDZt2oS6ujq58yVdWQ8ePMDrr7+OQYMGwdPTE8OGDcMHH3ygtMvh0qVLeOGFF9C/f394eHigX79+eOmll5CdnS13rqSPPjU1Fbt370ZwcDC6du2KsWPHAgDq6urw73//GzNnzpS+v4+PD6ZOnYrjx4/L3EsyHgMAeXl5Ml0mrfv7s7OzsWTJEuk9fX19MXfuXFy6dEnhZyouLsbixYvh5+eHzp07Y+zYsdi9e7fCc/XRuXNnfP755wgKCsLt27exadMmmdd/++03rF27Fo899hh8fX3h6emJQYMG4X//93+Rl5cnc+6iRYsQHR0NAIiNjZWpj9TUVABAeXk5PvroI0yZMgUDBgyAh4cHfH19MWvWLJw9e9bgn08fisZzJIQc17GE31lqaioiIyMxcOBAeHp6ok+fPnjsscfw2muvyf0da/qc2L17Nx555BEAQFpamkxZzaX7kC0gA9m9ezfq6+sRERGBAQMGqD3f1la26hsaGvDss8/i2LFj6NOnD55++mk4ODggLS0Nb775JlJSUrB//36F182YMQOFhYUICwuDra0tvv32W8TExKC6uhqrV6+WOX/fvn2IioqCvb09wsPD0a1bN2RnZ2P//v04duwYvvnmGwwZMkSuvJs3b8bp06cRHh6Oxx57DLW1tQCAP//8EytXrsSoUaMQEhKChx9+GIWFhThy5AgiIyPx4YcfYt68eQAAb29vrFixArGxsTLdJQBkxoRSUlIwd+5c1NTUYNKkSfD19UVBQQEOHz6MEydOICEhAaGhodLzS0tLMXHiRNy6dQujRo1CYGCg9FtvcHCw2t+FtmxsbLB8+XKkpaUhMTER77zzjvS1w4cPY+fOnRg3bhwCAgJgb28v7QY6evQoTp06hW7dugEAJk+ejPLychw5cgRBQUHSoC6pKwC4efMm3n77bQQGBmLSpElwdXVFXl4ejhw5gu+//x579uzBxIkTDf4Z2xpz/p199913iIyMhLOzs/RvsqysDL///ju2b9+OmJgY6d+9Ns+JwYMHY+HChdi2bRt69OiBOXPmSN+zZbmFxABkIJmZmQCAcePG6XT9Bx98gGPHjmHBggVYv349bGxsADR/21m6dCk+//xzxMfHY+HChTLXFRQUYMiQIfj666/h6OgIAFixYgVGjBiBbdu2Yfny5bCzswPwV6uie/fuOHLkCLp27Sq9T2pqKqZPn47Fixfj9OnTcuU7c+YMvvvuO7ng5OrqiqtXr0r/QCXKysowadIkrF27FpGRkRCJRPDx8cGqVasQGxurtLukvLwc8+fPh52dHU6cOIH+/ftLX7tx4wZCQ0MRHR2Ny5cvw8HBAQAQExODW7duYcGCBdiwYYP0/IULFyIsLEx95etgzJgxsLW1RXFxMXJzc6VdU5GRkYiKipKWTeL7779HZGQkNm7ciA8++AAAMGXKFOnDbOzYsQrro2/fvvj111/luoxu376N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\n",
      "text/plain": [
       "<Figure size 432x432 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = figure1(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Data Preparation V2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load data_preparation/v2.py\n",
    "\n",
    "torch.manual_seed(13)\n",
    "\n",
    "# Builds tensors from numpy arrays BEFORE split\n",
    "x_tensor = torch.as_tensor(x).float()\n",
    "y_tensor = torch.as_tensor(y).float()\n",
    "\n",
    "# Builds dataset containing ALL data points\n",
    "dataset = TensorDataset(x_tensor, y_tensor)\n",
    "\n",
    "# Performs the split\n",
    "ratio = .8\n",
    "n_total = len(dataset)\n",
    "n_train = int(n_total * ratio)\n",
    "n_val = n_total - n_train\n",
    "\n",
    "train_data, val_data = random_split(dataset, [n_train, n_val])\n",
    "\n",
    "# Builds a loader of each set\n",
    "train_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)\n",
    "val_loader = DataLoader(dataset=val_data, batch_size=16)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Configuration V4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting model_configuration/v4.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_configuration/v4.py\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model and send it at once to the device\n",
    "model = nn.Sequential(nn.Linear(1, 1))\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters\n",
    "# (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%run -i model_configuration/v4.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[0.7645]])), ('0.bias', tensor([0.8300]))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbs = StepByStep(model, loss_fn, optimizer)\n",
    "sbs.set_loaders(train_loader, val_loader)\n",
    "sbs.set_tensorboard('classy')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "Sequential(\n",
      "  (0): Linear(in_features=1, out_features=1, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "print(sbs.model == model)\n",
    "print(sbs.model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbs.train(n_epochs=200)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9414]], device='cuda:0')), ('0.bias', tensor([1.0233], device='cuda:0'))])\n",
      "200\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict()) # remember, model == sbs.model\n",
    "print(sbs.total_epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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9dVX9OrV65Jqyw0KwEGgFmAREZHLX+3atVmxYgWbN2+mVKlSvPHGGxw5ciTfA3D37t2pWrUq99xzD0899RTx8fE8/fTTGIZxwZbh5ORkfvnll4B5xYsXp1GjRnTu3JlRo0bx0ksvUaxYMZ5++mnKly/PzTffDMCECRNo1aoV9evXJyUlheXLl1OnTh0sFguffPIJR44coU2bNoSHh7N69WqSk5OpV69enp2HnFAADgK1AIuIiFz+xo0bx8GDB+nVqxdhYWEMGDCA//u//7tgH+DcZrPZ/K2v1157LTVq1OCZZ56hT58+OJ3OLLfdvn07HTp0CJjXpk0b/vvf/zJr1izGjh3L7bffjsvlom3btixevBiHw+E/7vjx4zlw4AAhISFER0fz3nvvAb4uEDNnzuS5554jJSWFmjVrMnPmTFq0aEFyAbj/qxEXF6f7b+WzAQPC+OSTtL48c+cm0qtX1v2FiqLY2Fjq1KkT7DIKLJ2frOn8ZE3nJ2s6P1k73/k5ffr0RT34obBJTk4+7wM08ssPP/xAly5d2LhxIw0aNAhqLenl5vm52M+cWoCDIONdIIJUiIiIiBQKy5YtIzw8nJo1a7J3717GjRtHy5YtC1z4LSgUgINAXSBEREQkN505c4ann36aP//8kzJlytChQwcmTpwY7LIKLAXgINAgOBEREclNAwYMCHjksmRN9wEOArUAi4iIiASPAnAQpG8BLgCDIUVERESKDAXgIEg/CC4xUS3AIiIiIvlFATgIQkMDp9UHWERERCT/KAAHQcbboKkFWERERCS/KAAHQcYWYAVgERERkfyiABwE6VuANQhORETk8vfuu+8SGRl53unMTJs2jebNm+f6sSVrCsBBoEFwIiIiBUPv3r3p2bNnpst27NhBeHg4q1evvqh9x8TEsGXLlkspLwO32014eDifffZZnh8rM88++yzt27fP8+PkNQXgIEjfBUItwCIiIsExYMAA1q5dy759+zIse++996hWrRodO3a8qH2HhoZSvnz5Sy2xwB2rMFAADgK1AIuIiBQM3bt3p0KFCsybNy9gvsvlYuHChdxxxx1YLL649Pjjj9OyZUsqVapE06ZNGT9+PCkpKefdd2bdEl566SXq1KlD1apVueeee0hMTAxY/sMPP3DzzTdTq1YtIiMjue666wJadps2bQrAHXfcQXh4uL/7RGbH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06dP59ChQx7zzJkzB7vd7vG44447uqjEneuFF15o9d0HDx7sft80TV544QWGDBlCdHQ0kydP5vDhw11Y4s6VnJzcqn7sdjs//elPgfbrr6fZuXMn9957L0OHDsVut7NmzRqP9zvSXmpqasjOziYuLo64uDiys7OpqanpzK9xzbRVP06nk2effZaMjAz69etHUlISjzzyCCdOnPBYxuTJk1u1qYceeqizv8o10V776Uhf3NTUxFNPPUV8fDz9+vXj3nvvpaSkpDO/xjXTXv1cqi+y2+3MmzfPPU9P3p51ZHve3fogCYA72YYNG5g/fz5PPvkkO3bsYOzYsdxzzz2tOlpv8Pnnn/Pwww+zdetWNm3ahM1mY9q0aVRXV3vMd+utt5Kfn+9+rFu3rotK3PkSExM9vvuFg6WXX36Z5cuX8+KLL/Kf//yHyMhIfvSjH3Hu3LkuLHHn2bZtm0fdbN++HU3TmDZtmnuetuqvp6mvr2fYsGEsXryYgICAVu93pL088sgj5Obmsm7dOtavX09ubi6zZ8/uzK9xzbRVPw0NDXz77bfMmzeP7du38/bbb1NSUsLdd9+Ny+XymHfmzJkebWrZsmWd+TWumfbaD7TfFy9YsID333+fv//972zevJlz584xffp0dF3vjK9wTbVXPxfWS35+PmvXrgXw6I+g527POrI97259kO2aLFVc1vLly7nvvvt44IEHAFiyZAmffPIJ//jHP3j22We7uHSda8OGDR6v//a3vxEXF8eePXuYNGmSe7qfnx9RUVGdXbxuwWazXfK7m6bJihUrmDt3LnfddRcAK1asIDExkfXr1zNr1qzOLmqni4iI8Hi9evVqQkJCPDY4l6u/nigrK4usrCwAHn30UY/3OtJe8vPz+fjjj9myZQvjxo0DYNmyZUyaNImCggISExM79wtdZW3VT2hoKP/61788pi1btowbb7yR/Px8hg8f7p4eGBjYI9tUW/XToq2+uLa2ltWrV7N8+XJuu+02QPXpycnJfPrpp0yYMOHaFLyTtFc/F9fL5s2bSUhIYPz48R7Te+r2rL3teXfsg2QPcCdqbm5m37593H777R7Tb7/9dr744osuKlX3UVdXh2EY2O12j+m7d+8mISGB9PR0fvWrX1FRUdFFJex8RUVFDB06lJSUFB566CGKiooAOH78OGVlZR5tKSAggIyMDK9s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\n",
      "text/plain": [
       "<Figure size 720x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = sbs.plot_losses()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Making Predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.5],\n",
       "       [0.3],\n",
       "       [0.7]])"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_data = np.array([.5, .3, .7]).reshape(-1, 1)\n",
    "new_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.9939734],\n",
       "       [1.6056864],\n",
       "       [2.3822603]], dtype=float32)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "predictions = sbs.predict(new_data)\n",
    "predictions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Checkpointing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbs.save_checkpoint('model_checkpoint.pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Resuming Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_configuration/v4.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[0.7645]], device='cuda:0')), ('0.bias', tensor([0.8300], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_sbs = StepByStep(model, loss_fn, optimizer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9414]], device='cuda:0')), ('0.bias', tensor([1.0233], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "new_sbs.load_checkpoint('model_checkpoint.pth')\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1.6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_sbs.set_loaders(train_loader, val_loader)\n",
    "new_sbs.train(n_epochs=50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsAAAAEQCAYAAAC++cJdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOzdd3gU5doG8HtmW3oBEloKECDUQAAJIL0EDl16EfhEOoJgOYCCIhYQUQSliBQFRZQickQERZpKB0V6QBISpEkSIG3bzPfH6IZJAiRhs5Ny/66LS6fszLM7u5t73n3nHSE5OVkGEREREVEJIWpdABERERGRKzEAExEREVGJwgBMRERERCUKAzARERERlSgMwERERERUojAAExEREVGJwgBMRERERCUKAzARERERlSgMwBqKiYnRugRyEh7L4oPHsvjgsSw+eCyLl8JwPBmAiYiIiKhEYQAmIiIiohKFAZiIiIiIShQGYCIiIiIqUfRaF0BERETFh81mQ2pqqlO36ebmhtu3bzt1m6QdZx1PT09P6PX5i7IMwEREROQUNpsNd+/ehZ+fHwRBcNp2TSYT3NzcnLY90pYzjqcsy0hOToa3t3e+QjC7QGhAkoD0dODuXR2Skpz3BUFERKSl1NRUp4dfopwIggA/P798/9rAFmAX+/13Ea1aef8zFYmICDv27k3RtCYiIiJnYfglV3mU9xpbgF3MZFJPm83a1EFERERUUjEAu1jWAJyRwTNlIiIiIldiAHYxk0lWTVssGhVCREREBW748OEYOnRonh7Tvn17TJ8+vYAqIoB9gF0u60WPGRna1EFERESAn5/fA5cPHDgQS5Ysyff258+fD1mWH77iPdavX5/v4b3yYubMmdi9ezd2795d4PsqbBiAXcxozNoCzC4QREREWjl37pzj/7dv346JEyeq5t1vuC6r1QqDwfDQ7fv6+ua5Jn9//zw/hvKGXSBcjC3AREREhUfZsmUd//4Nq1nnnT9/Hn5+fti8eTM6d+6MsmXL4osvvsCNGzfw1FNPoWbNmihfvjyaNm2Kr776SrX9rF0g2rdvj2nTpmHGjBmoVKkSqlevjlmzZqlaibN2gahevTref/99jB8/HkFBQahduzaWLl2q2s/Zs2fRsWNHlC1bFlFRUdi1axdKly6NjRs35vu1uXXrFkaOHInQ0FCUL18evXr1QkxMjGN5YmIiRowYgbCwMJQtWxaRkZFYsWKFY/lHH32EyMhIBAYGIiwsDH379s13Lc7GFmAX0+sBUZQhSUrLr90uwGZT5hMRERVHfn55bwVVy9vjk5ML5q5xM2fOxBtvvIE6derAZDIhPT0djRo1wuTJk+Hj44MffvgBY8eORXBwMJo2bXrf7Xz++eeYMGECdu7ciaNHj2LMmDGIjIxEt27d7vuYDz74AC+//DKef/55bN26FVOnTkWTJk1Qv3592Gw2DBo0CJUrV8bOnTtx9+5dvPTSS5Ak6ZGe78iRI3H16lWsW7cOXl5emDlzJvr06YNDhw7BZDJh5syZuHjxIjZs2IBSpUohNjbWcYe3AwcOYPr06fjoo4/QqFEjJCcnY8+ePY9UjzMxdmnAzQ1IS8ucNpsZgImIiAq78ePHo2vXrtnm/WvkyJHYtWsXNm3a9MAAHBERgRdffBEAEBYWhlWrVmHv3r0PDMAdO3bE8OHDAQATJkzA0qVLsW/fPtSvXx/bt29HfHw8tm/fjoCAAABKWO/Ro0e+n+upU6fw008/YefOnWjYsCEA4OOPP0adOnWwefNm9O/fH/Hx8YiMjERkZCQAIDQ01PH4+Ph4eHt7o1OnTvDw8EBISAgiIiLyXY+zsQuEBrKOBGE2sx8wERFRYfdv0PuXzWbDnDlz0KxZM1SqVAkVK1bEjh07EB8f/8Dt1K5dWzVdrlw53Lx5M9+PiYmJQUhIiCP8AkCjRo0e+nwe5Pz58zAajWjQoIFjXqlSpVC9enVHH+kRI0Zg7dq1aNGiBV555RXs37/fsW6HDh0QEBCAiIgIjBo1Cl9++WW+79pWEBiANZB9LGBt6iAiIqLc8/DwUE3PmzcPy5cvx+TJk/G///0P+/btQ4cOHWC1Wh+4nawXzwmC8NDuCg96jCzLTr8D34NGrvh3X126dMEff/yBMWPG4Nq1a+jduzeee+45AMroGj///DM+/vhjlC9fHnPnzkVUVNRDg76r8Id3DWQNwBwLmIiIirNH7ZObkZFx39EYtHTgwAF07drVcXGXJEm4ePEigoODXVpH9erVcfnyZfz9998oU6YMAODo0aOPtM3w8HBYLBYcO3bM0QUiMTER58+fx7hx4xzrBQQEYPDgwRg8eDDWrl2LiRMnYt68eRBFEQaDAW3atEGbNm0wdepUVKlSBT/++COeeOKJR6rNGRiANeDmpj6rUu4Gl7cxAomIiEhbVatWxfbt23Ho0CH4+vpi0aJFuHbtmssDcMeOHREUFISxY8fi1VdfRUpKCl577TUIgvDQluGMjAycOHFCNc/Lywu1a9dGu3btMGHCBLz33nvw9PTEa6+9hoCAAPTs2RMAMGvWLDRq1Ag1atSA2WzG1q1bUa1aNYiiiC1btuDatWto0qQJ/Pz8sGvXLmRkZCA8PLzAXoe8YADWgNGonmYXCCIioqJn2rRpSEhIwBNPPAEPDw8MHToU3bt3x9WrV11ah16vd7S+tm3bFpUqVcLrr7+OAQMGwJT1Z+cszp49i5YtW6rmNWnSBN9//z2WLVuGqVOnol+/frBarWjatCk2bNgA4z9BRq/XY+bMmYiPj4ebmxuioqKwZs0aAEoXiKVLl+Ktt96C2WxG5cqVsXTpUjRo0AAZhSD4CMnJyWx6dLEOHTxx+HDmuceOHSlo3NiuYUX0qGJiYlCtWjWtyyAn4LEsPngsXe/27dv5uvHDwxTWLhCF2ZEjR9C+fXvs378fNWvW1LocFWcez/y+59gCrAFeBEdERETOtHnzZvj5+aFy5cqIjY3FtGnT0LBhw0IXfgsLBmANcBg0IiIicqY7d+7gtddew19//YVSpUqhZcuWePPNN7Uuq9BiANZA1hZgs1mbOoiIiKh4GDp0qOqWy/RgHAdYA2wBJiIiItIOA7AG2AeYiIiISDsMwBrIOg6wxcIWYCIiIiJXYQDWAMcBJiIiItIOA7AGsg59x1shExEREbkOA7AGsl4Ep9wKmYiIiIhcgQFYAxwGjYiIqPhZvXo1QkJC7judk/nz5yMyMtLp+6YHYwDWAIdBIyIiKhz69++PHj165Ljs3Llz8PPzw65du/K17b59++Lo0aOPUl42NpsNfn5++Pbbbwt8Xzl544030Lx58wLfT0FjANZA1j7AbAEmIiLSxtChQ7F3717ExcVlW7ZmzRoEBwejVatW+dq2u7s7AgICHrXEQrev4oABWAPsA0xERFQ4dOzYEYGBgfj8889V861WK7788ks8+eSTEEUlLk2fPh0NGzZEuXLlEBERgZkzZ8L8gFasnLolvPfee6hWrRqCgoIwduxYpKWlqZYfOXIEPXv2RJUqVRASEoL//Oc/qpbdiIgIAMCTTz4JPz8/R/eJnPa1fPly1K9fHwEBAWjQoAHWrFnjWPZvS/Lq1asxZMgQVKhQAfXr18eGDRty+9LlKCkpCaNGjUJoaCjKly+PJ554AufOnXMsT05Oxrhx4xAWFoayZcuifv36WLZsmarmBg0aIDAwEGFhYejduzckSXqkmnLCWyFrIGsfYI4CQURExZmvn9+jPT6P699OTs71unq9HgMHDsTatWsxdepUR9jdtm0bbt26hcGDBzvW9fb2xuLFi1GuXDmcPXsWkydPhpubG6ZOnZqrfa1fvx5z5szBO++8g8cffxwbN27Ehx9+iDJlyjjWSUlJwcCBA/H2228DAJYtW4Y+ffrg+PHj8PPzw08//YQaNWpg0aJFaN++PfT6nKPc5s2bMW3aNMyePRutW7fGjh07MGnSJJQrVw4dOnRwrPf2229j5syZeO2117Bq1SqMGzcOTZs2RcWKFXP9Gt5r9OjRiIuLwxdffAEfHx/MmjULvXv3xpEjR+Dm5oZZs2bhwoULWL9+PcqUKYPY2FgkJSUBUML/1KlTsXTpUjRu3BjJycnYu3dvvup4GLYAa4AtwERERIXHkCFDkJCQgN27dzvmffbZZ2jbti2CgoIc86ZMmYKoqCiEhoaiY8eOmDRpEjZu3Jjr/SxZsgRPPvkkhg0bhqpVq2LKlCmOFt1/tW7dGv3790d4eDjCw8Mxb948iKKInTt3AoAjLPv6+qJs2bIoXbp0jvv64IMPMGjQIIwYMQJVq1bFuHHj0Lt3b7z//vuq9QYOHIi+ffuiSpUqmDFjBgDgwIEDuX5O9zp37hx27NiBhQsXolmzZqhTpw6WLVuG5ORkx+sUHx+PunXrokGDBggJCUHLli0dfbDj4+Ph5eWFTp06ISQkBBEREXjmmWccJyXOxACsAY4CQUREVHiEhYWhWbNm+OyzzwAAV69exc6dOzFkyBDVeps2bULHjh1RvXp1VKxYETNmzEBCQkKu93P+/Hk89thjqnmNGzdWTd+4cQPPPvssGjZsiJCQEAQFBSExMTFP+/l3X1FRUap5TZo0UXVHAIA6deo4/t9oNKJ06dK4efNmnvb1r3PnzkGv16NRo0aOeX5+fqhRo4Zjv08//TQ2bdqE5s2bY8aMGfjll18c67Zr1w7ly5dHvXr1MGrUKHzxxRdISUnJVy0PwwCsAV4ER0REVLgMHToUW7duRVJSEtauXQt/f3907tzZsXz//v0YOXIkOnTogHXr1mHv3r146aWXYHFyP8ZRo0bhxIkTmD17NrZv3459+/ahfPny+dqPIGT/hTnrvKxdKARByHefW1mW77vs3/126tQJhw8fxvjx43Hjxg307dsXEydOBAD4+Phg3759WLFiBSpUqIB3330XUVFRuH79er7qeRAGYA0YjRwGjYiISo7bycmP9O/6tWt5Wj8/evToAZPJhC+//BKfffYZBgwYAIPB4Fh+8OBBBAcH44UXXkCDBg0QFhaGy5cv52kf1atXx5EjR1TzDh8+rJo+cOAARo8ejejoaNSsWRMeHh6qAKjT6aDT6WC32x+6r6xdGQ4cOIDw8PA81ZwXNWrUgM1mUz3H5ORknD17VrXfMmXKYODAgfjoo4/w/vvv47PPPoPVagWgBPLWrVtj5syZ+Pnnn3H79m3s2LHD6bXyIjgNZG0BzsjQpg4iIiJSuLu7o2/fvpgzZw6Sk5OzdX8ICwtDQkICNmzYgIYNG+KHH37A119/nad9jBkzBhMmTEC9evXQrFkzfP311/j9999VF8GFhYXhyy+/RGRkJFJSUjBjxgyY7uk7KQgCgoKCsHfvXjRp0gQmkwl+OVxkOHHiRIwYMQIRERFo3bo1tm/fjo0bN2LdunV5fGWyy8jIwIkTJ1TzPD09ER4ejo4dO+LZZ5/F/Pnz4e3tjVmzZsHPzw+9evUCoIwjXKdOHdStWxdWqxXffvstwsLCYDAYsHXrVsTHx6NZs2bw8/PDnj17kJaWViChnS3AGuCNMIiIiAqfIUOGIDk5GVFRUdlCV7du3TBu3DhMmTIFLVq0wM8//4xp06blafv9+vXDCy+8gFmzZqFVq1aIiYnB6NGjVessXrwYt2/fRsuWLTFixAg89dRT2UZkePPNN7Fr1y7Url0bbdq0yXFfPXr0wOzZs/HBBx+gSZMmWL58OebPn68aASK/Lly4gJYtW6r+/fs8li5dioiICPTv3x8dOnSAxWLBxo0b4fZP65/BYMCbb76J5s2bo1OnTjCbzVi7di0Apb/w//73P/To0QONGzfGkiVLsGjRomz9pJ1BSE5Ovn+HDSoQMTEiHnvM2zEdFmbH0aMF08mbXCMmJgbVqlXTugxyAh7L4oPH0vVu374NX9+8Dlr2cBkZGY4ARUWfM49nft9zbAHWAFuAiYiIiLTDAKwBjgJBREREpB0GYA1wFAgiIiIi7TAAa4AtwERERETaYQDWgNGonjabBTxg7GgiIiIiciIGYA2IYk7dIDQqhoiIyIkedDcwImd6lPcaA7BG2A2CiIiKG09PTyQnJzMEU4GTZRnJycnw9PTM1+N5JziNKC3AmRe/KRfC8QuDiIiKLr1eD29vb9y5c8ep271z5w58fHycuk3SjrOOp7e3N/T6/EVZBmCN8HbIRERUHOn1eqffDOPGjRsIDg526jZJO4XheLILhEay3gzDYuFQaERERESuwACsEZNJPc0WYCIiIiLXYADWCG+HTERERKQNBmCNcBQIIiIiIm0wAGskp5thEBEREVHBYwDWiJubugsE+wATERERuQYDsEaytgBbLNrUQURERFTSMABrJHsLMLtAEBEREbkCA7BGsg6DxovgiIiIiFyDAVgjHAaNiIiISBsMwBrhjTCIiIiItMEArJGsfYB5K2QiIiIi12AA1kjWUSDYAkxERETkGiUiAA8YMAChoaEYOnSo1qU4ZG0BZh9gIiIiItcoEQF43LhxWLp0qdZlqGS/E5w2dRARERGVNCUiALds2RJeXl5al6Hi5qaeZgAmIiIicg1NA/Avv/yCAQMGoGbNmvDz88Pnn3+ebZ3ly5cjIiICZcuWRatWrfDrr79qUKnzZR0GjTfCICIiInINvZY7T01NRa1atTBw4ECMGTMm2/JNmzZh6tSpePfdd9GkSRMsX74cffv2xYEDBxAcHAwAaNq0aY7bXr9+PYKCggq0/keRdRg03gqZiIiIyDU0DcDR0dGIjo4GoPTTzWrRokUYNGgQhg0bBgB45513sHPnTqxcuRKvvvoqAGD//v2uK9iJ2AJMREREpA1NA/CDWCwW/Pbbb5gwYYJqftu2bXHw4MEC229MTEyBbftet275AKjumE5MTHPZvqlg8PgVHzyWxQePZfHBY1m8FPTxrFat2gOXF9oAfOvWLdjtdgQEBKjmBwQE4MaNG3naVo8ePXDy5EmkpaWhVq1a+OSTT9C4ceMc133YC+Ysf/2lU03r9Z4u2zc5X0xMDI9fMcFjWXzwWBYfPJbFS2E4noU2AP9LENRdA2RZzjbvYb755htnluQUbm6AHlaUQiJuoCxHgSAiIiJykUI7DFrp0qWh0+mytfb+/fff2VqFixLhyhV4duqElk/WQAbc8DOaA2AfYCIiIiJXKbQB2Gg0on79+ti1a5dq/q5duxAVFaVRVY9O9vKC/sABuN28Ah0khOAyBEgcBYKIiIjIRTTtApGSkoI///wTACBJEhISEnDixAn4+/sjODgY48ePx+jRo9GwYUNERUVh5cqVuHbtGp566ikty340vr6QfXwg3LkDADDBgkDcQEZGoMaFEREREZUMmgbg48ePo1u3bo7p2bNnY/bs2Rg4cCCWLFmCXr16ITExEe+88w6uX7+OmjVr4quvvkJISIiGVT86KTgYulOnHNOhiEO8hQGYiIiIyBU0DcAtWrRAcnLyA9cZMWIERowY4aKKXEMKCsoWgGMyHtOwIiIiIqKSo9D2AS7OpCwt2CG4DLOZF8ERERERuQIDsAakf27j/K9QxHEYNCIiIiIXYQDWgBwUpJoORRzsdgE2m0YFEREREZUgDMAayNoCHILLAICMDC2qISIiIipZGIA1kFMXCADsB0xERETkAgzAGpADAyEbjY5pfyTDG3dw8yYDMBEREVFBYwDWgihCqlhRNSsElxEXx8NBREREVNCYuDQi59ANggGYiIiIqOAxcWkkpwvhGICJiIiICh4Tl0akHIZCi43l4SAiIiIqaExcGslpJAi2ABMREREVPCYujeTUBeLyZRGyrFFBRERERCWE0wLwtWvXcPbsWWdtrtjL6SK4O3cEJCdzKDQiIiKigpTnALxq1SqMHj1aNe/5559HrVq10KxZM7Ro0QK3bt1yWoHFVdZh0CrgLxhhZj9gIiIiogKW57T16aefwtvb2zG9d+9erFy5En369MErr7yCS5cuYd68eU4tslgymWAJDHRMipDREdsRF8cWYCIiIqKClOcAHBcXhxo1ajimN2/ejIoVK2Lp0qWYNGkSRo4ciW3btjm1yOIquXlz1fRwrOSFcEREREQFLM9py2KxwGAwOKZ37dqF9u3bQxSVTVWpUgXXrl1zXoXF2K1u3VTTXbAViWdualQNERERUcmQ5wAcGhqK3bt3AwCOHTuG2NhYtG3b1rH8xo0bqi4SdH+ptWsjuWJma7oBNtQ4sk7DioiIiIiKvzwH4OHDh2Pz5s1o1qwZevXqhYoVK6JDhw6O5QcOHFB1kaAHEAQkdn9SNatd3KfgWGhEREREBSfPAXjEiBFYsGABqlSpgv/85z/YuHEj3N3dAQBJSUm4efMm+vbt6/RCiyvj0/1gg84xXc16Gjh0VMOKiIiIiIo3fX4eNHToUAwdOjTbfH9/f0f3CMod76qB2GHojM7W/znmSUs+gRjVSMOqiIiIiIovpww5YDabsWHDBixfvhxXrlxxxiZLlO+DnlZN+3y3AUJSkkbVEBERERVveQ7AL7zwAprfM3yXzWZDx44dMWrUKLz44oto0qQJTp065dQii7srEdGIRahjWmfJgGHtWg0rIiIiIiq+8hyA9+zZg44dOzqmv/76a/z++++YN28efvjhB5QuXRrvvPOOU4ss7mrUFvAR1HfXM65cCUiSRhURERERFV95DsBXr15FaGhma+V3332HOnXqYPjw4WjUqBGGDx+OQ4cOObXI4q5dOxtW4GlYkDm+su7iRej27tWwKiIiIqLiKc8BWK/XIz09HQAgyzL27t2Ldu3aOZb7+fkhMTHReRWWAPXr22EvVQYb0Ec137R8uUYVERERERVfeQ7AtWrVwldffYXk5GR89tlnSEpKQvv27R3LL1++jDJlyji1yOJOpwPatrVhCcaq5uu3bYPAiwqJiIiInCrPAXjKlCk4deoUqlSpgmeffRZRUVGqi+K2b9+OBg0aOLXIkqBdOxt+RnP8gTqOeYLdDuOnn2pYFREREVHxk+dxgFu1aoU9e/Zg165d8Pb2Ru/evR3LkpKS0Lx5c3Tp0sWpRZYEbdvaAAhYgrFYjPGO+cbVq2F+8UXAYLj/g4mIiIgo1/J1I4zw8HCEh4dnm+/v74/Zs2c/clElUdmyMiIi7PjsxJN4G1PgjRQAgHjtGvTffQdbjx4aV0hERERUPOQrAAPApUuXsGPHDly+fBkAEBISgujoaFSuXNlpxZU07dtb8d4JH6zBEIzDEsd80/LlDMBERERETpKvAPzyyy9j6dKlkLKMU/vSSy9hzJgxePPNN51SXEnToYMN770HLMFYVQDW79sH8fffIdWrp2F1RERERMVDni+CW7RoERYvXozOnTtjx44diIuLQ1xcHHbs2IEuXbpgyZIlWLx4cUHUWuxFRdkRFCThJOpiD1qqlpkWLtSoKiIiIqLiJc8BePXq1YiOjsaaNWvw2GOPwcfHBz4+PnjsscewevVqtG/fHp988kkBlFr8iSLQt68FADAX/1UtM3z9NYTYWA2qIiIiIipe8hyAY2NjER0dfd/l0dHRiIuLe6SiSrJ+/awAgG34D06itmO+IEkwLVqkVVlERERExUaeA7C/vz9iYmLuu/zChQvw9/d/pKJKspo1JdSta4cMMVsrsPGzzyDcuqVRZURERETFQ54DcOfOnbFixQp8/vnnkGXZMV+WZaxduxYrV67kOMCPqF8/pRvEOgxAPIIc84X0dBjWrtWqLCIiIqJiIc8B+JVXXkF4eDgmTJiA6tWro1OnTujUqRPCw8Mxfvx4hIeHY8aMGQVRa4nRp48VgiDDCiM+wATVMuPq1cA9Jx5ERERElDd5DsB+fn746aefMGfOHNSrVw+JiYlITExEREQE5s6di7Vr1yIhIaEgai0xypeXER1tAwB8gv+DBZl3gdPFxED3669alUZERERU5OVrHGCj0YhRo0Zh1KhR2ZbNmzcPb731FhITEx+5uJJs4kQztm834CYC8Q16oC82OJYZP/0U6Y8/rmF1REREREVXnluAyTWaNbOjYUOlFfhjjFQtM2zZAiQna1EWERERUZHHAFxICYLSCgwAP6I9LqFS5rKMDBi/+EKjyoiIiIiKNgbgQqxrVxuqVFGGRFuOEaplxmXLALtdo8qIiIiIii4G4EJMpwNGjVKGRFuBp2GGMXPZpUvQb9umVWlERERERVauLoI7evRorjf4119/5bsYyq5/fytefdUN183lsBaD8BQ+cSwzLV4MW9eu2hVHREREVATlKgC3b98egiDkaoOyLOd6XXo4f38Z3btbsX69Ee9jkioA63/9Fbrjx2GPjNSuQCIiIqIiJlcBeNGiRQVdBz3AkCEWrF9vxAnUw49oh/bY6VhmXLwY6R9/rGF1REREREVLrgLwoEGDCroOeoDmze2oXNmOS5d0eA/PqQKw4euvkTFzJuSKFTWskIiIiKjo4EVwRYAoAkOGWAEA36MTziLcsUyw2WBkCzARERFRrjEAFxEDB1qg08mQIWI+JquWmVatAlJSNKqMiIiIqGhhAC4iypeXER2t3BluDYbgb5R2LBNu3+aNMYiIiIhyiQG4CBk6VBkTOB0eWIoxqmXGxYt5YwwiIiKiXGAALkI6dLChXDkJALAI42GBwbFMd+kS9N9+q1VpREREREUGA3ARotcDgwcrrcDXUB5roR6dwzR/PiDLWpRGREREVGQwABcxTz5pdfz/XPxXtUz/22/Q797t4oqIiIiIihYG4CKmcmUJrVsrIfgMauFr9FQtN733nhZlERERERUZDMBF0FNPWRz/PwdTVcv0+/ZBd/iwq0siIiIiKjIYgIugzp1tCAxULoY7hCj8hDaq5aa339aiLCIiIqIigQG4CDIYgCefzGwFfhMvq5f/+CN0hw65uiwiIiKiIoEBuIgaOtQCQVBGfPgJbbEXLVTLTbNna1EWERERUaHHAFxEVaoko1072z9TAl7BLNVyw65d0P36q+sLIyIiIirkGICLsPHjM7tB7EHrbH2B3dgKTERERJQNA3AR1rq1DRERmbc/fhWvqZbr9+2Dbu9eV5dFREREVKgxABdhggBMmmR2TP+MFvhR6KBax232bN4djoiIiOgeDPBSDngAACAASURBVMBFXPfuVlSqlNkKPEPO0gq8fz90e/a4uiwiIiKiQosBuIjT64FnnsnsC3wATbFD/x/VOm5vvslWYCIiIqJ/MAAXA4MHW1CmjOSYftmWpRX48GHot2xxdVlEREREhRIDcDHg7g6MHp3ZCnwEj+F7Uzf1Oq+8AmRkuLo0IiIiokKHAbiYGDHCAk/PzG4Ok8xzYRf1jmkxLg7Gjz7SojQiIiKiQoUBuJjw95cxdGhmK/A51MDn3mNU67jNmwfhxg1Xl0ZERERUqDAAFyPjx5uh12e2Ak++PRNmT3/HtHD3LkxvvaVFaURERESFBgNwMRIUJKNvX6tjOhGlscBnumod4+rVEE+dcnVpRERERIUGA3AxM2mSGYKQ2Qo8/eozuFu+qmNakCS4vfwyh0UjIiKiEosBuJgJD5fQtavNMW2FEW/4zlWtY9i9G/rt211dGhEREVGhwABcDD33nFk1PfdsT/xdr5VqntvUqUB6uivLIiIiIioUGICLochIO9q0sd4zR8ALwruQxczDrYuNhendd11fHBEREZHGGICLqSlT1K3An/7WELGdR6nmmRYsgHj+vCvLIiIiItIcA3Ax1aRJ1lZgYPSNNyCVLeuYFqxWuD/3HCBJWR9OREREVGwxABdjU6eqW4F/OFQax4a8rZqn//lnGJcvd2VZRERERJpiAC7GoqLsaNs2SyvwzkGwtmunmuc2cybEixddWRoRERGRZhiAi7mXX1a3Ah87bsA3XRZB9vFxzBPS0uA+bhxgt7u6PCIiIiKXYwAu5ho2tKNbN3Ur8EtLqiL1DfUtkfUHD8I0Vz1eMBEREVFxxABcAkyfngFRzLzzW0yMDgtuPwVrx46q9Uxz50K3Z4+ryyMiIiJyKQbgEiA8XMKgQepW4BmvuOPrLoshBQY65gmyDI9RoyDcuOHqEomIiIhchgG4hJg6NQOenpmtwLIsYMgLlXHgmRWQBcExX7x+HR7DhgEWixZlEhERERU4BuASIihIxooVadDpMkOwxSKgz5LOSJv8gmpd/f79cJsyxdUlEhEREbkEA3AJ0qmTDR9+mK6ad/WqiDWVp8PWqpVqvmnVKhg/+siV5RERERG5BANwCTNwoBUjR6qHRlv0kSdSV66CFBqqmu8+ZQoMa9e6sjwiIiKiAscAXAKNH29WjQpx8qQOe08FIHXtWsienqp13Z95BoZNm1xdIhEREVGBYQAugSpVktGli001b/FiE6TatZG2ahVkg8ExX5AkuI8eDd3Ro64uk4iIiKhAMACXUGPHqrtBbN+uxx9/iLBFRyNtxQrIOp1jmWC1wmPYMAiJia4uk4iIiMjpGIBLqKZN7ahfP7MVWJYFTJjgDpsNsHXvjvTFi1XriwkJcB85ksOjERERUZHHAFxCCQLw3/+qW4F/+02PRYuMAABr//4wjx2rWm7YuRNeHTpAPHfOZXUSERERORsDcAnWubMNPXuqW3TfessN588rb4uMWbNgi4pSLdf9/ju8WrWCcelSQJJcVisRERGRszAAl3Bz52agVKnMIGs2Cxg+3APp6QAMBqStWgUpJET1GCEjA+5Tp8Kjd28IV6+6uGIiIiKiR8MAXMIFBsqYMydDNe/kSR1eeskNACBXqICUPXtgeeKJbI817NoFr06dINy44ZJaiYiIiJyBAZjQt68VvXqpu0KsWmXC+vXKcGiyvz/SV65E2kcfQfbxUa0nxsXBY+BAIC3NZfUSERERPQoGYIIgAO+/n44qVeyq+RMmuOPwYZ1jJWv//rj788+wPf64aj390aPwGDkSyFC3JBMREREVRgzABADw8QFWrUqDyZR5h7iMDAGDBnkgNlZwzJNDQpC6eTOsbduqHm/YuhVebdtCPHvWZTUTERER5QcDMDnUqyfhvffSVfNu3hTRv78nkpPvmfnPxXH2WrVU6+pOn4ZX69YwrlwJyDKIiIiICiMGYFIZPNiK555Td2U4d06HYcM8YbXeM9PXF6lffgl75cqqdYWMDLg/9xw8hgyBkJTkgoqJiIiI8oYBmLKZPt2MJ55QXxS3Z48ezz/vrmrYlYODkbJ7Nyx9+2bbhuHbb+HVogV0R48WdLlEREREecIATNmIIrB4cToee8ymmr96tRELFxrVK/v6In3ZMqQtWQLZy0u9nYQEeHbqBK9mzeDVoAHcR46E8PffBV0+ERER0QMxAFOO3N2BtWvTEBqqvtvbq6+645tv9OqVBQHWgQORsmcPbPXrqxdZrdCdPg3dn3/CuH49PKOjIV66VNDlExEREd0XAzDdV0CAjK++SoWPj/qCttGjPfDjj/ps60thYUjdsQPmMWPuu03dn3/CMzoahrVrodxujoiIiMi1in0ATkhIQJcuXRAVFYXHH38cW7Zs0bqkIiU8XMKaNanQ69XDo/Xp44kXX3RDamqWBxiNyJgzB6mffgrZ1zfHbYo3b8Jj3Dh416wJ0+uvQ7h1qwCfAREREZFasQ/Aer0es2fPxsGDB7F582ZMmzYNabxrWZ60amXH/PnZW2s//tiEbt08kZKS/TG2Hj1w58QJpOzcibv79sHSr1+2dcTkZLi9+y68IyJgeuMNwGLJviEiIiIiJyv2AbhcuXKIiIgAAAQEBMDX1xe32OKYZ0OGWPHaa+kQRXV3iGPH9Pi///NQD5H2L19f2Bs2hFS3LtKXLkXGCy9AFoRsqwmpqXCbNw+eTzwBITGxgJ4BERERkULTAPzLL79gwIABqFmzJvz8/PD5559nW2f58uWIiIhA2bJl0apVK/z666/53t/x48dhs9kQFBT0KGWXWM8+a8F336WicmX1LZN//NGACRPccw7B/xJFmKdPR8rRozBPmADJ3z/bKvpffoFn27YwbNwI3L4N3eHD0G/d+vCL5pKT8eCdExEREWXSNACnpqaiVq1amDNnDtzd3bMt37RpE6ZOnYrnn38ee/fuRePGjdG3b1/Ex8c71mnatGmO/xISElTbSkxMxJgxY/DBBx9AyKEVknKnSRM79u5NQb166hC8bp0RHTp44vz5B7+lpCpVkPH667h78iTS33gDUmCgarkuNhYeTz8N39BQeHXoAM/Bg+EdGQnPDh1gmj8f+m+/hfjnn8rKt2/DY+hQ+FaqBO/ISOiOHHHqcyUiIqLiSUhOTi4U96ytWLEi5s6di8GDBzvmtWvXDrVr18bChQsd8xo0aIAePXrg1VdfzfW2zWYzevbsiWHDhmHAgAFOrftRxMTEoFq1alqXkS/XrwuIjvZCXJw68Lq5yVi2LA3du9vu80g14dYteAwZAn0eW/ZtjRtDSE6G7vx5xzzZxwcpW7ZAql8fkCTof/wRuoMHATc32OvWhb1BA8hZArdTSBJiLlxAterVnb/tksRmg/6nnyCXLg17w4aalVGUP5ekxmNZhFgsEOPjIQUHA0ZjtsU8lsVLYTiehTYAWywWlC9fHitWrEDPnj0d673wwgs4ffo0vvvuu1xtV5ZljBgxAlWrVsW0adMeun5MTEz+nkAJFBdnwrhx4bhxQ/1lJYoypk+PRbduuetrLVitCH73XQRu3PjINdl8fZHcsiU8Tp+Gx8WL2Zan1K2LpDZtkFa9OsyhobB7eEDW6yG5uwOCAMPNmyj3ySfwOnkSqbVr41aXLkitVQvI6VcDWUbgF1+gwrJlkA0GXH7xRSRFRz/yc4DNBo9z52CpWBE2P79H3x4AMT0d+qQkWMqXz/m5aEx3+zaqPfssvE6dAgBcHzAA8c89l71WWYbPoUMQ09Nxu2lTyCZTztu7cwfG69eRUakSZIMBgsUCz9OnYff0RHpR+SMqScpdafLDbof7hQvQpaTA7usLc/nykDw9nV6f/08/QXfnDhKjoyFluRFOUSVYrZBFEdDptC7FKdxiYxG0YAEEux23OnVCYqdOuXpfiamp8N+5E5KHB243bw7Jza3AajRevYpqkybB/c8/kRESggvz5iGjcuUC29/9iBkZMNy8CX1iIuyensioWtUp2zXFxqL0tm2w+fkhMToattKlnbLd/HCPiYH7hQu4/fjjsPv4aFaHKzwsYBfaAHz16lXUrFkTW7duxeOPP+5Y7+2338b69etxJJc/d+/fvx+dO3dG7dq1HfM++ugj1bRWCsMZ0KNKTBQwaZI7tmwxZFs2cqQZ/ftb8fXXBuzdq0edOnbMnZsOb++ctyWePQvjqlUwfPUVxKQkSCEhkEqXhv748QJ+Fkrrsb1OHeh++w1CllFCpIoVYY+IgBwQoNzJzmqFvX59iAkJMH7xhWrd9LfeglShAnQnT0L29VWeQ61akLIe59RU6M6fh+zhAalqVccfWyEhAZ79+0N36hRkT0+kL1wIa48e0G/bBvHSJVh794ZcsWIOT0CG7uefoTtxArZ27SDVqAEkJ8Nt3jzot2+HeOECBFmGvW5dpC9cCNlkgnHdOoixsUBKCuDtDcsTT8DWowd0R47AuGgRBFmGrVEj2Fq0UFrVc3zh5PwH6n9afJCWBo8xY6D7J/z+yzx6NKydO0NMTIStWTPIgYFwf/ZZGFevBgBIwcFIf+MN2Lp3V9VgWLMG7lOnQkhNhVSmDGwdOkC/cyfEGzcc282YPfu+IeC+n8uMDOh/+AGG//0PYnw8rD17wvL004D+nzGxJQm633+HcPUqpCpVlOOqzz5etkNaGvS7dgGyDLlCBUihoZBLl4YQHw+3V16B4fvvYa9dGxmzZ8P+2GMQz51Ttl2tWs7vAQCQZeh37IDb9OnQ3XMyL7u5wTJ8ODJefRX456RBSEiA8fPPAUGAZdgwyGXLAgDEU6dg2LAB+t27Ifv6IuOttyDVqgVYrRDPnIFUvTpgMMD96adh3LzZcSxS162D9KDvVbsdQnw85PLlHTU8CuHmTbiPHQvd8eOwPvEEMmbOBO4N4bKM2F9+QdXkZIiXL8PetCnskZEQrlyB24wZEK9cgb1hQ1g7doS9WTPAYID+m28c7x1Lv37ImDEDyGk4R0lSRq3JTShMTYV+zx7Ifn6wN2784PfEg8gydIcOQXf8OIS7dwGzGVL16rC1aKG8pjm9RrGx8GrfHuI9d+C01a8Py7PPwtquHXCfACT+9hs8hwxRPp8ApFKlYHnqKVieeQayvz9gtUL/3XfQHz0K8fRpQBRhGTsWtjZtHvwc0tMh/P035HLlAIPyN0NISoJnp07QnTvnWE0KDET6woUQz56FkJwMqUoVxHl6okLPnpnflTduQLd/P/THjkFISoKlf3/Y78kKuWa1Qr99O4yffgr9Tz9BsGd277P06YP0Dz/M3XEGlOtRdDo4/sjZ7TB++CHc3nwTwj+jHMkGA6zdu8MyfDjsDRrA8NVX0O/aBal6dZgnTIBgtcL07rvQ79kDZGQAggB7kybKReShoXl/fvcwfPUV3MeOhWC3QwoIQOqWLZBq1nTUqt+xA7qjRwGjEbKXF+y1a8PepMkDP6+6X36B6Z13IKSnw9q1K6yDBkHORcAvDPmn0Afg7777Ds2aNXOsN2fOHGzcuBGHDx/WqlSnKQxvAGeQZWD1agMmT3aHJD04DPXqZcHKlbm4AYbZnPmHOi4Ohu+/hxgTA92pU9Dv369a1V6zJnRnzuS7flewh4fD1qIFhDt3IF64AN2JExBsSjcR2d0d9rp1YWvRQgmlV66oHiuFhkKMi1PW9fJC2urVsLVtC+Gvv5TX5MIFGD/9FLoTJ5R1dDqkL1gA05Il2UIlAMiCAEHO+WNva9BAOQmQ1HcAtHbqhLQVKwBPT4jnz8P46acwbNoE4dYtSOHhsEdGwtquHWzR0YCbG4Rr16A7exbimTPKf8+ehfjXX5BCQ2Ht1g3ipUswrl2r/CHPBdnTE9aOHWHctCl7zc2aIX3OHEghIXCbMwempUsfuj3LsGHImD4dssGgBBxBAGw2iCdPIuHiRVTo3Blwd4dw4wb0O3bAsH079Lt2Qcgy5p+9bl1Yu3eHmJAA/Y4dEK9ezazZaIQcGKh06ahZE5YhQ5SgJQgQbt6EZ7du0J09q9qeFBoK4eZN1UmYLIqQqldXrSsFByuh7/nnHQFNPHsWbi+/DMPOnfd93vaICGS8/DKE69fhPn06hDt3lO0FBMD80kvKH+Msny+pVClkvPYa3GbPVo6hn5/yh/unn1TryV5eSFu0CLbu3SH++ScMX3wB8epVSGFhQEoKjP9O/xNubJ06AVBCmtsbb0C/dy/sderAMmwY7PXqQYyLg2CxQKpaFVJIiLpF1myGZ9eu0N/zd8BerRrSP/wQ9rp1Ydi4EaaFC1UnAQCQMW0aDF98AV1srPp1qVQJ1gEDYJo7V/Xel8qVg61tW8ienoCHB2Q3N+jOnIFu716IycmQypRR3v9168LeqBHsdepAKldOee9cuwbDli0wLVjgCKBSQACs3bvD2rOn8l54QCuzePIkDFu2QPbzg1y+PIzLl0P/yy85H9eaNWHt0gW21q0Bmw1Cejrg5ga3KVNU3cRUx8tggK1dO5ifeUYJjmYzdH/8Af3OnTDNnw/BbM72GCk4GGkffAD3adNy/M619OoFOSAA4qVLkGrUgHnyZECSYHrnHeUk9OJFCJIE2dcXlqFDYWvYEKYlS6A/ePC+r4PqeVaqhIzXXoN+zx4YP/lEdaxkQUDGu+/CMny4+nWMiYFp/nzoDh6ErUULZLz2muMzI1y+DM8BA6A7ffq++7Q1a4a01ashlymTfWFyMgzffAPDDz9Ad/y447tbCgpSTmbj4iAmJ99327LJpHqd7VWqQLBYIGa5hglQTmLN48fD2quXckKaU8PD3bvKe95uV06sPTwge3tDLl8eupMn4fmf/6j2J5Urh9TvvgMsFrhPnAj9oUPZ9+vuDnu9epAqVFDe1zdvQrh7V/lMyjKMX32lXt9ggFSpEuQyZSDcvg3h2jXA2xu2Vq1g7dEDtnbtABSO/FNoA7CzukAUZoXhDeBM//ufHk8/7QGL5cEheO3aVHTunLs+wjkRz52DcdUqiOfOwdqjB6xDh8Lt1Vdh+uAD1XqyKMLasydkPz/ojxxxBMSiTtbrIVWqBN2FCy7dry0qCrKvLww7dtx3HdnbGxBFCLdvu7Ay5Y8fBCFbcM/VY318YA8Lg+7iRUcglE0mSJUrZwuoj8peqxYsTz8Nw5o10P/22yNvTypfHpb/+z8lcG7YoGq90oq9bl2l5e4hI7NY/rkew7Bpk6N17H5kg0FpsZdlSFWrPjCwFBVSYKAyTGRYGGC1QrhxA/D2hjU6GrozZ2CaM8dlx1Py94eYlOT87QYEAJIE0YVDj1r694cUFATh9m2If/4J/e7dqu8Fe40aSF23DvDyUlqec/k9aq9SBVJYmNLabrFAjIuD7siRh77PC4IUGAhbq1bKv9atIZcpA9Pbb8O0aFGOnyXZzQ0wGh3fb1qx9OuH9GXLABSO/FNoAzCgXARXp04dLFiwwDGvYcOG6N69e54ugiusCsMbwNn27tVh7FgPXLly/z5m5ctL2L//LpzUvdVBvHAB4vnzyh8SQYCtVSvIlSo5lgvx8TBs3aqcqV+4APHyZeVs2GxWnxWXKQPL6NHQHT8O/Q8/PPQLTgoOBm7cgJhDiwnlja1ZM4hnzhTIH+OSRhYE2CMjIV6+rPoJnEome+XK0D1sSMkcyF5e2X75KChS6dIuCcuytzdkLy/VLzaOGipUULr73Lzp1H1K/v6QS5cukIYL2ds717+maSltxQpYe/cGUDjyj27q1Kkztdp5SkoKzp49i+vXr2PNmjWoVasWfHx8YLFY4OvrC29vb8yePRvlypWDm5sb3nnnHfz666/48MMP4Xuf2+wWJYmJiSitYWf4ghAaKmPECAsqVJBx5owOt28L0OlkyHJmq3BKioCdOw0IDJRw86aAmBgdPD1lPOo1NHKpUpCqVYNUv77SZzVrwvb1hb1RI9i6dYN12DBYJk6EefJkmF94Adb+/WGPioK1Sxekv/MO7G3bwtq7N8zPPgtrt26wP/YYbG3bwjJ4MGwdOig/icoybO3aIW3NGsTWqIFSv/8O4fZt5efIvn1hj4wEPD0hXLmSY5cDe6VKgF6frc8xAOVn9atXVcFcFgQ8qG39fsvt9erh7sGDsDdtCv2vvzq+KG3NmiFjyhRYBg+GGB8P8a+/HI+RKlSAedIkCLduOf0Pwb0kPz/l58KyZWHt1w/pixfD1qmT0lpvtcJeowaEpCRHdxFAaQFPX7AAsNmg+3dIvKzbrVgRqevXw9a8ufKz8+DBML/0EvQ7djzSH3OpbFlYBg9WWuBz+AlY9vSEvVEj5WfoXO7HXrUq5HLllOf5T0uV5OcH8zPPKD8Z/3O/cVkUIdWsqfys+JCWQdvjjyPts89g+e9/YRk6FGJsbI6t2bLJBEhStveNrVUr2OvXV/XLzInk5wfzhAnQHTx43241BU328cnx53oAkHQ62P8ZMSbriay1dWvYGzeGePq06vnLggBrnz4QY2Ly9YtCjjV6eSldg5xwkmxt0wbWXr2U95ksQ7h+/aGvvS0qCqk//ghr166At7fyuX7ISabs4YH0Dz9E+gcfQLx+Hbo//sheS6dOME+cqHx//NPH/mFkd3fV5/lf9nr1kLpzJ2SdDrrDhwF3d9iio2Ht0QOyv7/yWcjyPO2VK8P6xBPQnTuX4zbvR7BYsn0+rdHRSFuxAubXX4d1yBDoDh3KsSvCA5+bwaD8EnVPnbKbG6y9eiFt7VpYJk+GrVkzCGlpjusyZB8f2Jo3z3ZyYh4zBumLFsFerx50R486vgfu93zyQgoNve+vdNboaFh79lTWSUzMVbC2V60Ky4gREK5eve/7ShZFZLz/PvDPkLeFIf9o2gK8b98+dOvWLdv8gQMHYsmSJQCUG2EsWLAA169fR82aNfHWW2+pLoorygrDGVBBkmXH9VWYNcuE9967/4UEOp2MHj2sGDfOgkaNtP8ZN69iYmJQrUoV5YYcWS6YEBITlZbka9cglykDuWxZ2OvWVS46kmUIV69Cv2eP0kfuyhVY+/VTfta+cAGmhQsh6/WwDBsG3aVLcB89OvNiCr1e6ZtVuTKkqlVh7d0b+u+/h/uMGY59S+XLI2XnTsgVKigz0tOh378fUlCQcjHTv2w2GFesgGHrVtgjI5ExebJyApGcDM9+/bL1DbM1bgzLiBGwtWgB3enTSj/ZzZshXr+u1ObhAXt4OKQaNWCvWRNSjRqQypWDftcuGL7/HjAaYRk8GNYePR56QZR4+jQ8nn4aujNnHOHX+s8vRfodO+D28suOvp72WrVgjY5WLtbJoc+eEBsL9xdegO7kSSWkpqYq/SX/fb1Kl4bVYIDp2rXM5xoZCVvHjrB17Ah7vXqOi+d0v/wC/Y8/QrDZIJUtC6laNdhatnR8wePOHQiJiY4+2vqtW7MFKluDBkjdskW5eCs9XekbfuMGbC1aAH5+EBITYVi3DhBFWLt2hRwUBKSlwfTuuzAtWJDtj74UGor011+HrVs3dR9BWYZ++3YYtm51/Pphr1ULGW+9BTEuDu6TJ0OMj4ft8ceRMX067E2bApIE9zFjHH38ZB8fpM+bB92BAzBs3Qr5n7689shIiH/8AbdZs2D44YfMWoKCYPm//4Nw9SqEjAylC01wMNzHj1edbAHK+9T8/PMQT5+GYft25QKvypUBvR7iuXMQ73OHSNnbGyk//gjxwgUYP/kEupMnIV69CtnTE5b+/XGuZ0+EtmwJ3ZEj8Ojb1/HH2da0KVI3bgQ8PKA7cgTuEydCd/q0EvrmzIF16FAI168rwzPevQshLU05UU1Lg+zrC3vz5rDXrg0xIQHiyZPQHz0K3bFjEBISIF67BpjNkAMDIVWsqJw4jx0L2dNTef9//TUM27bl6udoqUwZ2Nq3hxgXB6lCBViGDoW9VSvVOkJSEvTffads86+/AE9PJWSmpQHp6Uq/71dfVTcIyDJ0v/4K08KFyusNJaDIFSrAFhUFe/PmsHbpkjlspCwr3czuGZLUPGGC0p9WFAGrFcbVq6E7cgRSaChgscD04YeZ31UmE8z//S/MI0cCBgMM69fDsHUrhPR05UKrxx6DtXv3zAsEbTZlu/dcqHplyxZUX7AA+qNHIXt4wPz88zA/8wxgMkF38CA8+ve/b39be40akEJDHc81K2ubNkj78kv18GsZGTC9/z4MX3/9wJMhe1gYrH37wtqpk9I3V5aV9VNSIAUHK10mcrjgVrhxA2JMjPKd4uUF/TffKK+v3Q7z5Mmw9eiRufKdOzB++SX0O3dC/8svDwylUpkykCpVUv62pKVB+PtvVSOGNToaaatXw+3ll5XrMNLTlX67NWooDT69e2d+d8gyxEuXIFy+rHxmLRble9XDA+L58xAvXoRUubLS79rNTdlnYiKEa9eUC8Z9fCCVLQvduXMQz5yBZdw4Rx2FIf8Umi4QJVFheAO4SkYG0KaNF86cefjQQo0b2zBunBldu9ryfcG0q7nqWIp//gn9tm2QgoOVC15yuIrbuHQpTPPnQ6pQAemLF2de5Ztfd+/Cfdo06Ldtgz0iAuZJk2Bv2TL7RRh2O8Q//4RsMEAOCcn/EF45sdmgO3QIUoUKqm4tjv2eOQM5IMAxkkGuSRKEK1cgXrqk/IJQqxZiLlxAuChmjrhQrpxTnoKQkADjJ5/A+OmnEG/efPDFNbkgnj4N46pVgCRBqlRJGRGgdev8jbAgScrZatb3kyTBsHEjxIQEWPr1U48+kcMIILpff4Vh61ZIVarAMmhQ5snAPYTERBg+/xzCnTuQAwKU93KrVoCHx/3rS0tTWtZSUmDYsgX6778HZBkZ06dnH6EkJQXw9AQEQfW5FOLjYVy5ErK/PywjR6prkySIFy5A9veHHBCQq5fsvmRZ+feg97/ZDN3p0xBjYiDGxkJ2d4dcpgx0x47BsHkzhL//hq1LF6S/+27e39N5decOhKQk5STZkH00n3vpt2yBftcu2Dp0gK1zrESgHgAAHgpJREFU5weuK8bEKO9PWYZl+PDsI+HkUUxMDKpVrQohLk55TbK8t4Rr16D//nsId+5AMJshu7tDCg5WGgjq1AEAmObPV04c7zn5sDVpgtQNG/DAnyDT0pRW5itXlBMcnU4Z4adyZeVEzZVDS1qt0B07Bv2uXdDv2QPd4cOOE2HL0KFIf/119cglsgzh8mXojx2DbDLB1rFj5sWXjzKKzyMqDPmHAVhDheEN4Eo3bwp4/vmch0zLSbVqdkydakajRjbYbAIqVpTg5qY0sn7xhQHHjunQvbsNbdvm/4I6Zylpx7I4K/BjKUlK4ChVqlCOyVycFMnPpSwrLQY5nDyUZE47lhaL0i/+4kVAr1dOvopKS0tO7t6F7vffIZcrpwy9WEQUhs9mET7qVNQEBMhYvToNv/8uYtEiE06d0sHbW8bffwu4cCF7y3BMjA5PP53ZKuTuLqNPHyt++02HP/5Q1v/kExPGjjXj9dczivR3GJUgopircTKphBIEht+CZDQqQ+sVobD4QN7esDdvrnUVRRIjA7lcvXoSli3L7Hcpy8BPP+mxeLERO3fev3U4PV3AmjXZb5G5ZIkJ27bpERgow81NuR2zv7+Mbt2s6NLFxkY2IiIq0cxmpacTz60yMQCT5gQBaNfOhnbtbDh7VsQHH5iwbp0Bdnvuk2tsrA5ZxrbHl18aUbeuHYGBEs6eVUaaaNPGhscft+HuXQHJyQJKlZJRoYKEKlUkBAXJEAQgNRVIShJQrpzs9FZlsxmIjRVRqZLkjJthUQ5SU5Vjf+mSiD59LKhXzzlX8juTLAMHDuiwZ48elStL6N3byl8wKF/sdqW7cVE+0ZdlZRz5nTsNaNLEhn79rMXlTtR59scfIiZOdMeZMzoMGWLBW29lPKxr9kOtWmXEjBlusNmASZPM+O9/zU69RKOoYh9gDRWGPjCF1YULIt57z4Sff9ZDkoD0dODWLfUn1sdHxp07zvvWL1NGgq+vjD//FCHLAkqVkhAdbUNQkOTYj4+PjFKlZNSoIaFWLfv/t3fv4VHU5wLHv7O7ud825LIhhEQgN0IIMeH2RKICCuVw8NYqIGrrDR6enlY56iNUW2vtIyAVikdrT7UHb7RyiPCAFU2r0nKLBJUYuRMgJFySkCu5b3Zmzh8/s7AkkOgBkpj38zx5kszM7s7OO7/Zd2be32/RdVXbXFBwGi+vGHQd0tJ0UlNV0lVVpdE+YtWaNd781395U11tISTE5I47nIwfr2OxQEiIyahROiEhJtu32/jqK1UeMnKkTnS0gculUVGhsXevleJiC0FBJgMHGgQEqA7Thw5Z2LzZRlmZhfh4g5kznaSm6tTUaFgsMGyYwcCBJmfPQlmZhbIyjfJyCz4+JpmZOjExJg0NcPKkhaYmjZYWCA83iY83qK+Hd9/1Zt8+C2FhJtnZLoKDobRUo7LSQl2dRmsrpKbq3HCDq9NvV3U61ddmDxhgenS0bnf2LHzwgRd5eTZ8fEymTnWRne2itlajpkbDbjcJDzdxudTJSXW1mt7YqOHnZ+LvD2fPauzfb+Gll3woL/9mpAarya9/3UJ2tosdO9S+lJhoEBlpUF1tob4ehg41GD7cwGqFykqNPXuOk5QUR329xo4dNvbvV+974kQX3t4m//qXjcOHrTid6opKTIzByJHqOZubNZqboalJw+kEu93Ebjc5dMhKfr6VigoLvr4mx45ZOHTo3Cf8uHEu/vu/m4iJMWluVn1UbDY6bCvThCNHLBQVWYiKMkhONigv1/jsMxv19RpJSTp+frB6tRe5uV44HAZ33dXG7be3ERxs4uNzrv+LywUHD6rtlJho4OUF9fVQVGSlsRFaW1W8UlJ0fHzUdj92zEJbm3p/27db2brVhq7D9de7uPXWNtraNEpLLZSWqt+BgSY33+xy7+eg2vIXX1hpadGIjjY4dszC//6vN0eOWBg40ODf/72N5GSDhgaNhgYV19ZWjdhYg8REHV9fqK/X2L1bnUCUl2skJRlcf70LqxVOndLw94dRo3ROnDjG3r0J5OfbqK1Vz+frCw6HQXS0SVqazrXX6gwebODnB2VlGoWFVgoLrXz1lRrGcdQonTvuaCMhQae1VcPX1yQgoOM+3N7OrVb1d1GRhZISCxERBkOGqP1t3z4LLhdce61ObKzZ4fF79ljYtk2148GDDZKTdfz9VaxCQkyGDTPc+0Rxscby5b7k5HgRHGwya5aTn/zEyTXXmO595dAhC5s2efHxxzbq6jQGDzaIilLbtrFRIyVF5557nFitsH69F6dOWUhMNMjIcFFebuHQIQsWC8TGGsTGGsTFGQQGqv2msNBKSYmFU6csBASYTJ/eRlaW2ghlZRonT1o4fVrD1xfS03UiIjpPNRoa4JFH/HjvvXM7e3q6i/vua+P4cQtWq2p7EREHSE7u+HnpcsGBAxY+/dTGl19acbk0vL1NHA6TrCwXEybohIaq125qgi+/tFJaaqG8XKOuTsPp1HC5ICjIJCRELdfSohEcbDJhgouQEJM33vDmk09sxMSY/Nu/tXHTTS7Cw02Pk46zZ6G01EJrq4bFYhIUBEOGGO2DZHDokAXDUHF0uTRKSzXOntUYNswgMdFA12HdOi/+8z/9aG4+98Q339zGm2824e8P+/ZZWLXKm5IStX8kJho4HAahoSYxMSZxcQZNTbBli43iYrVMQYG1wwhMU6e28dBDThobYe9eK/v3W/H3N5k82cWoUTq7d1s5ftxCbKzBpEkugoPV56I69liprtZITtaZMsWFYcCePVZMU7U5h6N9G6rX8vbuvD9ob8h/JAHuQb1hB+grXC7YtMnGmjXeNDfD3LlOMjN1/uM//MjN/X+eHl8B3t4mbW14jH/cHTabOjheCVaredGr6kFBJvX1HecNGGDQ1qZ1Ou9ir5GYaDBwoEooT59WyXZlpToC+viopCMqSp281NVBXZ3GqVPqg6OnBASYmKZK7HoTf3+V+AcGqg/b8vJz2xJA08xvvY8NHGgQGWlSVGShsVE91tfXJDLSpKSk4yeVt7c66Ssr++6XjAYOVHdZvLxMdu60eXzA9xa+viYtLd1br9BQdULp49MeF5XsmaZKcJxOrct9KTLSwGZTJwQtLVq3tonNZhIdrfbV06e1To8VAwcaDBpkcPiwSuC7omntCXP32/jFjiNxcQa1tVqnrxsdbWC3m/j7m/j5qX27oUGjqMjiPmG9lLAwJ4MHq4S8qUm9xtmzmnsfvpRBg1SiuGePtctvKu2ugADVNtva1Nj2nV2MCQ42SUjQ2b/fesn9wcfHxOm8eAxiYtT6f/HFpW8T+fqa6Dq0tfVc+4qMNGhs9IxLSIhJYqLOgw86mTVLjcfdG/IfSYB7UG/YAb4PTp1SV0dbWtSVy8ZGjZwcL9av7+RSoxBCCCGuuqVLm5k3T40N3RvyH6k6E31edLS6KnK+6dNdLFzYys6dVnx91e35I0cs5OZ6ceqURliYujVdWalu1e7bd+4MXdPULfXuXFkQvZOfn4mXF5e1ROZys9nU1fJ9+/ppsaMQF4iIMDhzRopTfXxMBg82Oh0d6bsaMED1cyks7LnjTVJS7/qSK0mAxfdWUpJBUtK5DlApKQYzZnQ+ZrDLBYcPW2hp0UhIUDWPeXlW8vJU3WhwsLrVefasxokTFvbsUbVQfn4mEREmAQGNXHONL83NmrvWE9StUl9fVQMbGmpyxx1tPPywk127rHz8sarb1HXVMa799tygQQYTJ7poalK1VQ0NqqYtIEAdQBITVa3p6dMabW2qVjQoyGT8eJ34eIO//c3Gxx970damXrO5WdV11terelmHQ92+jYw0KS9XtZStrRo2m0lMjEFIiErODh+2uhPIyEiDOXOcVFZa+OILdQCNiTGIilIjbjQ1wb/+ZfOoaz2fpqmauEslpHFxBrff7qSqysLf/majpkbVSoeHq9uqVVUWbDZ1Oz40VP0OCDBpatJoalLfOBgRoeri7rvPidMJ8+b5s3OnjaAgkxtvdBEaanL4sKpbDgsz8fY2KSiwuuvLAwNNgoOd6LoXpqlOnEaPVidP7TXEY8fqZGW5sNvV7caDB63s3WuhuVkjIEDd3m1PwGtqVK1yaKjJ+PEuhg83cLlUh6XMTJ3wcJP/+R9vXnrJh+PHLWiaerxhqLrBzm41Bwaq2vBTpywcP67quEeP1hk0SN3irarSSE3VueeeNoqLLaxd60VpqardvfAWf0SEKlVpL2/QNFVnGhGharWPHLFw4oSa5+WlasKDgkysVhg82GDyZBcuF+TkeHHggJXQUPXBHRtrEBNj8PXXVjZt8upw+zcmRtWTlpWp6Tfc4GLaNBcFBVY2b7bhdKr3GRSk9m1Ng6NHVe2zpqnbz1FRJtdd5yIhwSA/30pBgRV/f1UCUFGhsXu3jcZGkzFjVF1xSorxzf6iShYOHrSwe7eVAwesnDmjygl8fExGjNBJS9NJSzPw9zd5/30vtm614XKpcpCGhs5LDy4UFqb2xTNnNI4fV3WyI0YYGAbs3m3ttNwiPNxg/HidlBSd4mJVc2mafFPbbOHkSc/kMCFBZ8GCVlwueOcdb3bvtnrc/g4ONhk92sX06S5SU3VOnLBQWanqW5uaNN5+24uCApUGpKerMdX37rVy6JCFyEiT4cNV7XZJidrXSkpUqVJEhEFGhk5Skqop3rHDxqZNNgxDc++jQ4aoUqjKSo2vv7Ze8ra8t7fJ3XerDl95eTZWrfLGalXvr6RE1TE3NHT+eE1Tx4OxY3UmTnQRFaWOjwUFqkb9wAGLRzuKi1M1zlFRJmFh58pYGhpUWYXFotZn3z4r27bZaGzUGD5c5yc/cXLmjEZurhdHjlg67NPe3ur4GRSk6rlLSy0epSAOh0FYmEldnYamqbIMf3+TPXus7qR/0CC1XZ94ooXYWINHH/VjwwYvj9KIzEwX992njsVHj1qoqdGoqlKlJO3HsWHDdMaOVfvQ4cMWEhIMVq5sZvBggxUrfNi1y4rFor73JC7OIDVVLfvhh15UVqqa+uRknS+/tPLll1Y0TdWBx8cbDBum2tHmzTZ277bh62uSmqpjmlBYeC7OXl4mFgsepW3nfx73BlIC0YN6wy0AcXmcH0vThLo69cVWnXX4upjWVnXVOTTUvOw9uk1T1Rr6+XXsLd7aCrW1KiE8fyQCXVedS0wTEhK6N2pFZaXGiROqptcwYOBA011zarOpcpWvvrLS3Kw6toWEqB+7XX0YnfcNnDidnl9q9l16u5um6qQYGmpetCe1aapOO97eMGCASVHR1W+XncXHNFXHmjNnLDQ3q/99fVWnvfY4NTerE6Du9hJ3OnF3ToqKUkmKpqm4VFdrDBlidOjgVVamasDj4oxvtT+3UydgFqqqVJ3k0KEGI0YYV3zUAtNU7TIxsetYmqbqAOjv3/V3Iuh6ey22Rlub6kAVEaFGk7HZVE27YdChk9T5WltVQuvlpU54fH3VEI5djXxQVweVlapjmL+/et3zX6OtTZ20lJVpxMWpTlGX6u3f3qnSZjPdnecuxTBUR7JvvmjPQ0WF5u7IGBtrerxuS4t6v+rbmdUJq+rAqhLDmBij086z7ZqbYfPmE0RFxaHr6gQzJMQkOFidJHXxpXscPao6vV1zjdGt99murU295wuPfaZ57uTWx0fFbsAAz/dsGGrblpaqUX/a29qF2p/Lz8/sdIiyykqNkydV7X90tOr4erH9qrJS7XuRkZcvrWttVb87O/63n8y377fNzaptqGO7mmcYcPKkxuHDViZOPDcsaW/IfyQB7kG9YQcQl4fE8vtDYvn9IbH8/pBYfr/0hnhKsY0QQgghhOhXJAEWQgghhBD9iiTAQgghhBCiX5EEWAghhBBC9CuSAAshhBBCiH5FRoEQQgghhBD9ilwBFkIIIYQQ/YokwEIIIYQQol+RBFgIIYQQQvQrkgALIYQQQoh+RRJgIYQQQgjRr0gC3ANef/110tLScDgc3HDDDezYsaOnV0l0YfHixdjtdo+fxMRE93zTNFm8eDHJyclERUUxffp09u/f34NrLNpt376dWbNmMXz4cOx2O6tXr/aY353Y1dbWMnfuXGJjY4mNjWXu3LnU1tZezbchvtFVPOfPn9+hrd50000ey7S2tvLEE08wdOhQoqOjmTVrFidPnryab6PfW758ORMnTmTw4MEMGzaMmTNnsm/fPo9lpG32Dd2JZW9sl5IAX2Xr1q1j4cKFPPbYY2zZsoWxY8dy5513Ulpa2tOrJrqQkJDAwYMH3T/nn7isXLmSV155haVLl/Lpp58SERHB7bffTn19fQ+usQBobGwkJSWFJUuW4Ofn12F+d2L30EMPUVhYyNq1a8nJyaGwsJB58+ZdzbchvtFVPAFuvPFGj7a6du1aj/mLFi3i/fff589//jObNm2ivr6emTNnouv61XgLAti2bRsPPvggubm5bNy4EZvNxm233UZNTY17GWmbfUN3Ygm9r13KOMBX2eTJkxkxYgQvvfSSe1pGRga33norzzzzTA+umbiUxYsXs3HjRvLy8jrMM02T5ORkHn74YR5//HEAmpubSUhI4LnnnuP++++/2qsrLmLQoEG88MILzJkzB+he7A4ePMi4ceP46KOPGD9+PAB5eXlMmzaNXbt2kZCQ0GPvp7+7MJ6grjRVV1ezZs2aTh9TV1dHfHw8r7zyCnfddRcAJ06cYOTIkeTk5DB58uSrsu7CU0NDA7GxsaxevZpp06ZJ2+zDLowl9M52KVeAryKn00lBQQGTJk3ymD5p0iR27tzZQ2sluqu4uJjhw4eTlpbGAw88QHFxMQDHjx+nvLzcI65+fn5kZWVJXHu57sQuPz+fwMBAxo0b515m/PjxBAQESHx7qby8POLj48nMzOTnP/85Z86ccc8rKCigra3NI+YxMTEkJSVJPHtQQ0MDhmFgt9sBaZt92YWxbNfb2qXtijyr6FRVVRW6rhMREeExPSIigoqKih5aK9Edo0eP5g9/+AMJCQlUVlaybNkypkyZwmeffUZ5eTlAp3E9ffp0T6yu6KbuxK6iooKwsDA0TXPP1zSN8PBwabe90E033cSMGTOIi4ujpKSE3/72t9xyyy3885//xMfHh4qKCqxWK2FhYR6Pk+Nwz1q4cCEjR45k7NixgLTNvuzCWELvbJeSAPeA8xsrqNuwF04TvcvNN9/s8f/o0aNJT0/nL3/5C2PGjAEkrn1ZV7HrLI4S397phz/8ofvvESNGkJ6ezsiRI8nNzeWWW2656OMknj3nF7/4BZ999hkfffQRVqvVY560zb7lYrHsje1SSiCuorCwMKxWa4ezmcrKyg5nuaJ3CwwMJDk5maNHj+JwOAAkrn1Qd2IXGRlJZWUlpnmuu4RpmlRVVUl8+4CBAwcSHR3N0aNHARVPXdepqqryWE7aa89YtGgR7733Hhs3buSaa65xT5e22fdcLJad6Q3tUhLgq8jb25v09HQ2b97sMX3z5s0eNUyi92tpaeHw4cM4HA7i4uJwOBwecW1paSEvL0/i2st1J3Zjx46loaGB/Px89zL5+fk0NjZKfPuAqqoqTp8+7U6o0tPT8fLy8oj5yZMn3R2qxNXz5JNPkpOTw8aNGz2GlQRpm33NpWLZmd7QLq0LFy789RV5ZtGpoKAgFi9eTFRUFL6+vixbtowdO3bw8ssvExIS0tOrJy7i6aefxtvbG8MwKCoq4oknnuDo0aOsWLECu92OruusWLGC+Ph4dF3nqaeeory8nN///vf4+Pj09Or3aw0NDRw4cIDy8nLefvttUlJSCA4Oxul0EhIS0mXswsPD+fzzz8nJySEtLY2TJ0+yYMECMjIyZLilHnCpeFqtVn7zm98QGBiIy+Xi66+/5mc/+xm6rrNs2TJ8fHzw9fWlrKyM1157jdTUVOrq6liwYAHBwcE8++yzWCxyXehqePzxx3n33Xd54403iImJobGxkcbGRkBdLNI0TdpmH9FVLBsaGnplu5Rh0HrA66+/zsqVKykvL2f48OE8//zzXHfddT29WuISHnjgAXbs2EFVVRXh4eGMHj2ap556iuTkZEDddluyZAlvvPEGtbW1ZGZm8rvf/Y6UlJQeXnOxdetWZsyY0WH67NmzefXVV7sVu5qaGp588kk+/PBDAKZNm8YLL7zQoZezuPIuFc/ly5czZ84cCgsLqaurw+FwkJ2dzVNPPUVMTIx72ZaWFn75y1+Sk5NDS0sL119/PS+++KLHMuLKuljbefLJJ1m0aBHQveOqtM2e11Usm5ube2W7lARYCCGEEEL0K3KvRwghhBBC9CuSAAshhBBCiH5FEmAhhBBCCNGvSAIshBBCCCH6FUmAhRBCCCFEvyIJsBBCCCGE6FckARZCCHFRdrudBQsW9PRqCCHEZSUJsBBC9KDVq1djt9sv+vPRRx/19CoKIcT3jq2nV0AIIQQsXLiQIUOGdJielpbWA2sjhBDfb5IACyFELzB58mTGjBnT06shhBD9gpRACCFEH9Bei7tu3TrGjRuHw+EgKyuL3NzcDsuWlpby8MMPM3ToUBwOBxMmTOCvf/1rh+VM0+S1115jwoQJREVFMXToUG677TZ27NjRYdl//OMfZGdn43A4yMjIICcnx2O+y+Vi2bJlZGZmup9rypQpbNiw4fJtBCGEuEzkCrAQQvQCZ8+epaqqqsP0sLAw9987d+5k/fr1zJs3j8DAQN58803mzJnDhg0buO666wCoqqriBz/4ATU1NcydO5eoqCjWrVvH/Pnzqa2tZf78+e7ne+SRR3jrrbe48cYbufvuuzFNk/z8fPLy8sjKynIvt2vXLj744APuv/9+7r33Xt566y3mzp3LyJEjSUpKAmDJkiW8+OKL3HvvvWRmZtLY2EhhYSGff/45t95665XabEII8Z1otbW1Zk+vhBBC9FerV6/mpz/96UXnnzhxgsDAQOx2OwC5ubmMGzcOgOrqajIyMkhMTOTvf/87AE8//TQvv/wyGzZs4IYbbgDA6XQybdo0Dhw4wL59+wgJCWHr1q3MmDGDH//4x6xcudLjNU3TRNM0QF15ttlsbN++3Z3sVlRUkJqayrx583juuecAyM7OJjo6mjVr1lzGrSOEEFeGXAEWQoheYOnSpe4E83x+fn7uv6+99lp38gswYMAA7rzzTl577TVqa2ux2+3k5uaSlpbmTn4BvL29mT9/Pg899BDbtm1j+vTpbNy4EVAJ84Xak9922dnZHusWGRlJQkICxcXF7mlBQUHs37+foqIi4uPjv/0GEEKIq0gSYCGE6AUyMjK67AQ3bNiwi04rLS3FbrdTUlLCjBkzOizXnsCWlJQAcOzYMSIiIoiIiOhy3QYPHtxhmt1up6amxv3/okWLuOeeexg9ejTJyclMmjSJH/3oR2RkZHT5/EIIcbVJJzghhOgjLrwyC6pcoTsuXO78MoeuWK3WLp8zOzubr776ildffZW0tDTeffddJk+ezPLly7v1GkIIcTVJAiyEEH1EUVFRh2lHjx4Fzl2ljY2N5dChQx2WO3z4sHs+wNChQ6moqODMmTOXbf3sdjuzZ8/mT3/6E3v37iUrK4ulS5ei6/plew0hhLgcJAEWQog+Yvfu3eTn57v/r66uZu3atYwZM8bdSW7q1KkUFhayZcsW93JtbW388Y9/xN/fnwkTJgBwyy23APD88893eJ3uXlU+X3V1tcf/fn5+JCUl0draSlNT07d+PiGEuJKkBlgIIXqBTz75xH0193zp6enu+t2UlBRmzpzJ3Llz3cOg1dfX86tf/cq9fPtYwbNnz2bevHk4HA7Wr1/Prl27eP755wkJCQFUycLdd9/NqlWrKC4uZsqUKYAa8mzEiBE89thj32r9x44dS1ZWFhkZGQwYMIA9e/bw1ltvMXXqVIKCgr7rZhFCiCtCEmAhhOgFlixZ0un05557zp0Ajxs3juzsbJYsWUJxcTHDhg3jnXfeITs72718WFgYubm5PPvss6xatYqmpibi4+N59dVXmT17tsdzv/zyy4wYMYK3336bZ555hsDAQEaNGuUeU/jbmD9/Ph9++CFbtmyhpaWFQYMG8eijj/Loo49+6+cSQogrTcYBFkKIPsBut3P//fezYsWKnl4VIYTo86QGWAghhBBC9CuSAAshhBBCiH5FEmAhhBBCCNGvSCc4IYToA2pra3t6FYQQ4ntDrgALIYQQQoh+RRJgIYQQQgjRr0gCLIQQQggh+hVJgIUQQgghRL8iCbAQQgghhOhXJAEWQgghhBD9yv8B2quUnGmqEjUAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 720x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = new_sbs.plot_losses()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9414]], device='cuda:0')), ('0.bias', tensor([1.0233], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(sbs.model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Putting It All Together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load data_preparation/v2.py\n",
    "\n",
    "torch.manual_seed(13)\n",
    "\n",
    "# Builds tensors from numpy arrays BEFORE split\n",
    "x_tensor = torch.as_tensor(x).float()\n",
    "y_tensor = torch.as_tensor(y).float()\n",
    "\n",
    "# Builds dataset containing ALL data points\n",
    "dataset = TensorDataset(x_tensor, y_tensor)\n",
    "\n",
    "# Performs the split\n",
    "ratio = .8\n",
    "n_total = len(dataset)\n",
    "n_train = int(n_total * ratio)\n",
    "n_val = n_total - n_train\n",
    "\n",
    "train_data, val_data = random_split(dataset, [n_train, n_val])\n",
    "\n",
    "# Builds a loader of each set\n",
    "train_loader = DataLoader(\n",
    "    dataset=train_data,\n",
    "    batch_size=16,\n",
    "    shuffle=True\n",
    ")\n",
    "val_loader = DataLoader(dataset=val_data, batch_size=16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load model_configuration/v4.py\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model\n",
    "model = nn.Sequential(nn.Linear(1, 1))\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters \n",
    "# (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 200\n",
    "\n",
    "sbs = StepByStep(model, loss_fn, optimizer)\n",
    "sbs.set_loaders(train_loader, val_loader)\n",
    "sbs.set_tensorboard('classy')\n",
    "sbs.train(n_epochs=n_epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9414]], device='cuda:0')), ('0.bias', tensor([1.0233], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
